@@ -10,8 +10,7 @@ namespace JT1078.DotNetty.Core.Configurations | |||||
public int TcpPort { get; set; } = 1808; | public int TcpPort { get; set; } = 1808; | ||||
public int UdpPort { get; set; } = 1808; | public int UdpPort { get; set; } = 1808; | ||||
public int WebSocketPort { get; set; } = 1818; | |||||
public int HttpPort { get; set; } = 1818; | |||||
public int QuietPeriodSeconds { get; set; } = 1; | public int QuietPeriodSeconds { get; set; } = 1; | ||||
@@ -0,0 +1,13 @@ | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.DotNetty.Core.Interfaces | |||||
{ | |||||
public interface IJT1078HttpBuilder | |||||
{ | |||||
IJT1078Builder Instance { get; } | |||||
IJT1078Builder Builder(); | |||||
} | |||||
} |
@@ -4,9 +4,9 @@ using System.Net; | |||||
namespace JT1078.DotNetty.Core.Metadata | namespace JT1078.DotNetty.Core.Metadata | ||||
{ | { | ||||
public class JT1078WebSocketSession | |||||
public class JT1078HttpSession | |||||
{ | { | ||||
public JT1078WebSocketSession( | |||||
public JT1078HttpSession( | |||||
IChannel channel, | IChannel channel, | ||||
string userId) | string userId) | ||||
{ | { | ||||
@@ -16,7 +16,7 @@ namespace JT1078.DotNetty.Core.Metadata | |||||
LastActiveTime = DateTime.Now; | LastActiveTime = DateTime.Now; | ||||
} | } | ||||
public JT1078WebSocketSession() { } | |||||
public JT1078HttpSession() { } | |||||
public string UserId { get; set; } | public string UserId { get; set; } | ||||
@@ -9,19 +9,19 @@ using JT1078.DotNetty.Core.Metadata; | |||||
namespace JT1078.DotNetty.Core.Session | namespace JT1078.DotNetty.Core.Session | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// JT1078 WebSocket会话管理 | |||||
/// JT1078 http会话管理 | |||||
/// </summary> | /// </summary> | ||||
public class JT1078WebSocketSessionManager | |||||
public class JT1078HttpSessionManager | |||||
{ | { | ||||
private readonly ILogger<JT1078WebSocketSessionManager> logger; | |||||
private readonly ILogger<JT1078HttpSessionManager> logger; | |||||
public JT1078WebSocketSessionManager( | |||||
public JT1078HttpSessionManager( | |||||
ILoggerFactory loggerFactory) | ILoggerFactory loggerFactory) | ||||
{ | { | ||||
logger = loggerFactory.CreateLogger<JT1078WebSocketSessionManager>(); | |||||
logger = loggerFactory.CreateLogger<JT1078HttpSessionManager>(); | |||||
} | } | ||||
private ConcurrentDictionary<string, JT1078WebSocketSession> SessionDict = new ConcurrentDictionary<string,JT1078WebSocketSession>(); | |||||
private ConcurrentDictionary<string, JT1078HttpSession> SessionDict = new ConcurrentDictionary<string, JT1078HttpSession>(); | |||||
public int SessionCount | public int SessionCount | ||||
{ | { | ||||
@@ -31,14 +31,14 @@ namespace JT1078.DotNetty.Core.Session | |||||
} | } | ||||
} | } | ||||
public List<JT1078WebSocketSession> GetSessions(string userId) | |||||
public List<JT1078HttpSession> GetSessions(string userId) | |||||
{ | { | ||||
return SessionDict.Where(m => m.Value.UserId == userId).Select(m=>m.Value).ToList(); | return SessionDict.Where(m => m.Value.UserId == userId).Select(m=>m.Value).ToList(); | ||||
} | } | ||||
public void TryAdd(string userId,IChannel channel) | public void TryAdd(string userId,IChannel channel) | ||||
{ | { | ||||
SessionDict.TryAdd(channel.Id.AsShortText(), new JT1078WebSocketSession(channel, userId)); | |||||
SessionDict.TryAdd(channel.Id.AsShortText(), new JT1078HttpSession(channel, userId)); | |||||
if (logger.IsEnabled(LogLevel.Information)) | if (logger.IsEnabled(LogLevel.Information)) | ||||
{ | { | ||||
logger.LogInformation($">>>{userId},{channel.Id.AsShortText()} Channel Connection."); | logger.LogInformation($">>>{userId},{channel.Id.AsShortText()} Channel Connection."); | ||||
@@ -55,7 +55,7 @@ namespace JT1078.DotNetty.Core.Session | |||||
} | } | ||||
} | } | ||||
} | } | ||||
public IEnumerable<JT1078WebSocketSession> GetAll() | |||||
public List<JT1078HttpSession> GetAll() | |||||
{ | { | ||||
return SessionDict.Select(s => s.Value).ToList(); | return SessionDict.Select(s => s.Value).ToList(); | ||||
} | } |
@@ -7,7 +7,7 @@ using System.Security.Claims; | |||||
using System.Security.Principal; | using System.Security.Principal; | ||||
using System.Text; | using System.Text; | ||||
namespace JT1078.DotNetty.WebSocket.Authorization | |||||
namespace JT1078.DotNetty.Http.Authorization | |||||
{ | { | ||||
class JT1078AuthorizationDefault : IJT1078Authorization | class JT1078AuthorizationDefault : IJT1078Authorization | ||||
{ | { |
@@ -14,27 +14,30 @@ using JT1078.DotNetty.Core.Session; | |||||
using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
using JT1078.DotNetty.Core.Interfaces; | using JT1078.DotNetty.Core.Interfaces; | ||||
namespace JT1078.DotNetty.WebSocket.Handlers | |||||
namespace JT1078.DotNetty.Http.Handlers | |||||
{ | { | ||||
public sealed class JT1078WebSocketServerHandler : SimpleChannelInboundHandler<object> | |||||
public sealed class JT1078HttpServerHandler : SimpleChannelInboundHandler<object> | |||||
{ | { | ||||
const string WebsocketPath = "/jt1078live"; | const string WebsocketPath = "/jt1078live"; | ||||
WebSocketServerHandshaker handshaker; | WebSocketServerHandshaker handshaker; | ||||
private static readonly AsciiString ServerName = AsciiString.Cached("JT1078Netty"); | |||||
private static readonly AsciiString DateEntity = HttpHeaderNames.Date; | |||||
private static readonly AsciiString ServerEntity = HttpHeaderNames.Server; | |||||
private readonly ILogger<JT1078HttpServerHandler> logger; | |||||
private readonly ILogger<JT1078WebSocketServerHandler> logger; | |||||
private readonly JT1078HttpSessionManager jT1078HttpSessionManager; | |||||
private readonly JT1078WebSocketSessionManager jT1078WebSocketSessionManager; | |||||
private readonly IJT1078Authorization iJT1078Authorization; | private readonly IJT1078Authorization iJT1078Authorization; | ||||
public JT1078WebSocketServerHandler( | |||||
JT1078WebSocketSessionManager jT1078WebSocketSessionManager, | |||||
public JT1078HttpServerHandler( | |||||
JT1078HttpSessionManager jT1078HttpSessionManager, | |||||
IJT1078Authorization iJT1078Authorization, | IJT1078Authorization iJT1078Authorization, | ||||
ILoggerFactory loggerFactory) | ILoggerFactory loggerFactory) | ||||
{ | { | ||||
this.jT1078WebSocketSessionManager = jT1078WebSocketSessionManager; | |||||
this.jT1078HttpSessionManager = jT1078HttpSessionManager; | |||||
this.iJT1078Authorization = iJT1078Authorization; | this.iJT1078Authorization = iJT1078Authorization; | ||||
logger = loggerFactory.CreateLogger<JT1078WebSocketServerHandler>(); | |||||
logger = loggerFactory.CreateLogger<JT1078HttpServerHandler>(); | |||||
} | } | ||||
public override void ChannelInactive(IChannelHandlerContext context) | public override void ChannelInactive(IChannelHandlerContext context) | ||||
@@ -43,7 +46,7 @@ namespace JT1078.DotNetty.WebSocket.Handlers | |||||
{ | { | ||||
logger.LogInformation(context.Channel.Id.AsShortText()); | logger.LogInformation(context.Channel.Id.AsShortText()); | ||||
} | } | ||||
jT1078WebSocketSessionManager.RemoveSessionByChannel(context.Channel); | |||||
jT1078HttpSessionManager.RemoveSessionByChannel(context.Channel); | |||||
base.ChannelInactive(context); | base.ChannelInactive(context); | ||||
} | } | ||||
@@ -69,12 +72,6 @@ namespace JT1078.DotNetty.WebSocket.Handlers | |||||
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, BadRequest)); | SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, BadRequest)); | ||||
return; | return; | ||||
} | } | ||||
// Allow only GET methods. | |||||
if (!Equals(req.Method, HttpMethod.Get)) | |||||
{ | |||||
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, Forbidden)); | |||||
return; | |||||
} | |||||
if ("/favicon.ico".Equals(req.Uri)) | if ("/favicon.ico".Equals(req.Uri)) | ||||
{ | { | ||||
var res = new DefaultFullHttpResponse(Http11, NotFound); | var res = new DefaultFullHttpResponse(Http11, NotFound); | ||||
@@ -83,17 +80,24 @@ namespace JT1078.DotNetty.WebSocket.Handlers | |||||
} | } | ||||
if (iJT1078Authorization.Authorization(req, out var principal)) | if (iJT1078Authorization.Authorization(req, out var principal)) | ||||
{ | { | ||||
// Handshake | |||||
var wsFactory = new WebSocketServerHandshakerFactory(GetWebSocketLocation(req), null, true, 5 * 1024 * 1024); | |||||
this.handshaker = wsFactory.NewHandshaker(req); | |||||
if (this.handshaker == null) | |||||
if (req.Uri.StartsWith(WebsocketPath)) | |||||
{ | { | ||||
WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel); | |||||
// Handshake | |||||
var wsFactory = new WebSocketServerHandshakerFactory(GetWebSocketLocation(req), null, true, 5 * 1024 * 1024); | |||||
this.handshaker = wsFactory.NewHandshaker(req); | |||||
if (this.handshaker == null) | |||||
{ | |||||
WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel); | |||||
} | |||||
else | |||||
{ | |||||
this.handshaker.HandshakeAsync(ctx.Channel, req); | |||||
jT1078HttpSessionManager.TryAdd(principal.Identity.Name, ctx.Channel); | |||||
} | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
this.handshaker.HandshakeAsync(ctx.Channel, req); | |||||
jT1078WebSocketSessionManager.TryAdd(principal.Identity.Name, ctx.Channel); | |||||
jT1078HttpSessionManager.TryAdd(principal.Identity.Name, ctx.Channel); | |||||
} | } | ||||
} | } | ||||
else { | else { | ||||
@@ -133,6 +137,8 @@ namespace JT1078.DotNetty.WebSocket.Handlers | |||||
// Generate an error page if response getStatus code is not OK (200). | // Generate an error page if response getStatus code is not OK (200). | ||||
if (res.Status.Code != 200) | if (res.Status.Code != 200) | ||||
{ | { | ||||
res.Headers.Set(ServerEntity, ServerName); | |||||
res.Headers.Set(DateEntity, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); | |||||
IByteBuffer buf = Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes(res.Status.ToString())); | IByteBuffer buf = Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes(res.Status.ToString())); | ||||
res.Content.WriteBytes(buf); | res.Content.WriteBytes(buf); | ||||
buf.Release(); | buf.Release(); | ||||
@@ -142,17 +148,23 @@ namespace JT1078.DotNetty.WebSocket.Handlers | |||||
Task task = ctx.Channel.WriteAndFlushAsync(res); | Task task = ctx.Channel.WriteAndFlushAsync(res); | ||||
if (!HttpUtil.IsKeepAlive(req) || res.Status.Code != 200) | if (!HttpUtil.IsKeepAlive(req) || res.Status.Code != 200) | ||||
{ | { | ||||
task.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(), | |||||
ctx, TaskContinuationOptions.ExecuteSynchronously); | |||||
task.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(), ctx, TaskContinuationOptions.ExecuteSynchronously); | |||||
} | } | ||||
} | } | ||||
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception e) | |||||
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) | |||||
{ | |||||
logger.LogError(exception, context.Channel.Id.AsShortText()); | |||||
context.Channel.WriteAndFlushAsync(new DefaultFullHttpResponse(Http11, InternalServerError)); | |||||
jT1078HttpSessionManager.RemoveSessionByChannel(context.Channel); | |||||
CloseAsync(context); | |||||
base.ExceptionCaught(context, exception); | |||||
} | |||||
public override Task CloseAsync(IChannelHandlerContext context) | |||||
{ | { | ||||
logger.LogError(e, ctx.Channel.Id.AsShortText()); | |||||
ctx.Channel.WriteAndFlushAsync(new DefaultFullHttpResponse(Http11, InternalServerError)); | |||||
jT1078WebSocketSessionManager.RemoveSessionByChannel(ctx.Channel); | |||||
ctx.CloseAsync(); | |||||
jT1078HttpSessionManager.RemoveSessionByChannel(context.Channel); | |||||
return base.CloseAsync(context); | |||||
} | } | ||||
static string GetWebSocketLocation(IFullHttpRequest req) | static string GetWebSocketLocation(IFullHttpRequest req) |
@@ -1,14 +1,15 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
<Import Project="..\Version.props" /> | <Import Project="..\Version.props" /> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>netstandard2.0</TargetFramework> | <TargetFramework>netstandard2.0</TargetFramework> | ||||
<LangVersion>7.3</LangVersion> | <LangVersion>7.3</LangVersion> | ||||
<Copyright>Copyright 2019.</Copyright> | <Copyright>Copyright 2019.</Copyright> | ||||
<Authors>SmallChi(Koike)</Authors> | <Authors>SmallChi(Koike)</Authors> | ||||
<PackageId>JT1078.DotNetty.WebSocket</PackageId> | |||||
<Product>JT1078.DotNetty.WebSocket</Product> | |||||
<Description>基于DotNetty实现的JT1078DotNetty的WebSocket服务</Description> | |||||
<PackageReleaseNotes>基于DotNetty实现的JT1078DotNetty的WebSocket服务</PackageReleaseNotes> | |||||
<PackageId>JT1078.DotNetty.Http</PackageId> | |||||
<Product>JT1078.DotNetty.Http</Product> | |||||
<Description>基于DotNetty实现的JT1078DotNetty的http服务</Description> | |||||
<PackageReleaseNotes>基于DotNetty实现的JT1078DotNetty的http服务</PackageReleaseNotes> | |||||
<RepositoryUrl>https://github.com/SmallChi/JT1078DotNetty</RepositoryUrl> | <RepositoryUrl>https://github.com/SmallChi/JT1078DotNetty</RepositoryUrl> | ||||
<PackageProjectUrl>https://github.com/SmallChi/JT1078DotNetty</PackageProjectUrl> | <PackageProjectUrl>https://github.com/SmallChi/JT1078DotNetty</PackageProjectUrl> | ||||
<licenseUrl>https://github.com/SmallChi/JT1078DotNetty/blob/master/LICENSE</licenseUrl> | <licenseUrl>https://github.com/SmallChi/JT1078DotNetty/blob/master/LICENSE</licenseUrl> | ||||
@@ -25,11 +26,10 @@ | |||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> | <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> | ||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" /> | <PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\JT1078.DotNetty.Core\JT1078.DotNetty.Core.csproj" /> | |||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="" /> | |||||
<ProjectReference Include="..\JT1078.DotNetty.Core\JT1078.DotNetty.Core.csproj" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -0,0 +1,24 @@ | |||||
using JT1078.DotNetty.Core.Interfaces; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.DotNetty.Http | |||||
{ | |||||
class JT1078HttpBuilderDefault : IJT1078HttpBuilder | |||||
{ | |||||
public IJT1078Builder Instance { get; } | |||||
public JT1078HttpBuilderDefault(IJT1078Builder builder) | |||||
{ | |||||
Instance = builder; | |||||
} | |||||
public IJT1078Builder Builder() | |||||
{ | |||||
return Instance; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
using JT1078.DotNetty.Core.Codecs; | |||||
using JT1078.DotNetty.Core.Impl; | |||||
using JT1078.DotNetty.Core.Interfaces; | |||||
using JT1078.DotNetty.Core.Session; | |||||
using JT1078.DotNetty.Http.Authorization; | |||||
using JT1078.DotNetty.Http.Handlers; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using System.Runtime.CompilerServices; | |||||
namespace JT1078.DotNetty.Http | |||||
{ | |||||
public static class JT1078HttpDotnettyExtensions | |||||
{ | |||||
public static IJT1078HttpBuilder AddJT1078HttpHost(this IJT1078Builder builder) | |||||
{ | |||||
builder.Services.TryAddSingleton<JT1078HttpSessionManager>(); | |||||
builder.Services.TryAddSingleton<IJT1078Authorization, JT1078AuthorizationDefault>(); | |||||
builder.Services.AddScoped<JT1078HttpServerHandler>(); | |||||
builder.Services.AddHostedService<JT1078HttpServerHost>(); | |||||
return new JT1078HttpBuilderDefault(builder); | |||||
} | |||||
} | |||||
} |
@@ -8,7 +8,7 @@ using DotNetty.Transport.Channels; | |||||
using DotNetty.Transport.Libuv; | using DotNetty.Transport.Libuv; | ||||
using JT1078.DotNetty.Core.Codecs; | using JT1078.DotNetty.Core.Codecs; | ||||
using JT1078.DotNetty.Core.Configurations; | using JT1078.DotNetty.Core.Configurations; | ||||
using JT1078.DotNetty.WebSocket.Handlers; | |||||
using JT1078.DotNetty.Http.Handlers; | |||||
using JT1078.Protocol; | using JT1078.Protocol; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
using Microsoft.Extensions.Hosting; | using Microsoft.Extensions.Hosting; | ||||
@@ -20,28 +20,28 @@ using System.Runtime.InteropServices; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace JT1078.DotNetty.WebSocket | |||||
namespace JT1078.DotNetty.Http | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// JT1078 WebSocket服务 | |||||
/// JT1078 http服务 | |||||
/// </summary> | /// </summary> | ||||
internal class JT1078WebSocketServerHost : IHostedService | |||||
internal class JT1078HttpServerHost : IHostedService | |||||
{ | { | ||||
private readonly JT1078Configuration configuration; | private readonly JT1078Configuration configuration; | ||||
private readonly ILogger<JT1078WebSocketServerHost> logger; | |||||
private readonly ILogger<JT1078HttpServerHost> logger; | |||||
private DispatcherEventLoopGroup bossGroup; | private DispatcherEventLoopGroup bossGroup; | ||||
private WorkerEventLoopGroup workerGroup; | private WorkerEventLoopGroup workerGroup; | ||||
private IChannel bootstrapChannel; | private IChannel bootstrapChannel; | ||||
private IByteBufferAllocator serverBufferAllocator; | private IByteBufferAllocator serverBufferAllocator; | ||||
private readonly IServiceProvider serviceProvider; | private readonly IServiceProvider serviceProvider; | ||||
public JT1078WebSocketServerHost( | |||||
public JT1078HttpServerHost( | |||||
IServiceProvider serviceProvider, | IServiceProvider serviceProvider, | ||||
ILoggerFactory loggerFactory, | ILoggerFactory loggerFactory, | ||||
IOptions<JT1078Configuration> configurationAccessor) | IOptions<JT1078Configuration> configurationAccessor) | ||||
{ | { | ||||
this.serviceProvider = serviceProvider; | this.serviceProvider = serviceProvider; | ||||
configuration = configurationAccessor.Value; | configuration = configurationAccessor.Value; | ||||
logger=loggerFactory.CreateLogger<JT1078WebSocketServerHost>(); | |||||
logger=loggerFactory.CreateLogger<JT1078HttpServerHost>(); | |||||
} | } | ||||
public Task StartAsync(CancellationToken cancellationToken) | public Task StartAsync(CancellationToken cancellationToken) | ||||
@@ -66,14 +66,15 @@ namespace JT1078.DotNetty.WebSocket | |||||
{ | { | ||||
IChannelPipeline pipeline = channel.Pipeline; | IChannelPipeline pipeline = channel.Pipeline; | ||||
pipeline.AddLast(new HttpServerCodec()); | pipeline.AddLast(new HttpServerCodec()); | ||||
pipeline.AddLast(new HttpObjectAggregator(65536)); | |||||
pipeline.AddLast(new HttpObjectAggregator(int.MaxValue)); | |||||
pipeline.AddLast("chunkedWriter", new ChunkedWriteHandler<IHttpContent>()); | |||||
using (var scope = serviceProvider.CreateScope()) | using (var scope = serviceProvider.CreateScope()) | ||||
{ | { | ||||
pipeline.AddLast("JT1078WebSocketServerHandler", scope.ServiceProvider.GetRequiredService<JT1078WebSocketServerHandler>()); | |||||
pipeline.AddLast("JT1078HttpServerHandler", scope.ServiceProvider.GetRequiredService<JT1078HttpServerHandler>()); | |||||
} | } | ||||
})); | })); | ||||
logger.LogInformation($"JT1078 WebSocket Server start at {IPAddress.Any}:{configuration.WebSocketPort}."); | |||||
return bootstrap.BindAsync(configuration.WebSocketPort) | |||||
logger.LogInformation($"JT1078 Http Server start at {IPAddress.Any}:{configuration.HttpPort}."); | |||||
return bootstrap.BindAsync(configuration.HttpPort) | |||||
.ContinueWith(i => bootstrapChannel = i.Result); | .ContinueWith(i => bootstrapChannel = i.Result); | ||||
} | } | ||||
@@ -0,0 +1,101 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using JT1078.DotNetty.Core.Session; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
using DotNetty.Common.Utilities; | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Handlers.Streams; | |||||
using DotNetty.Transport.Channels; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using System.Net; | |||||
using Microsoft.AspNetCore; | |||||
using Microsoft.AspNetCore.Cors; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using JT1078.DotNetty.TestHosting.HLS; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace JT1078.DotNetty.TestHosting | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// -segment_time 5秒切片 | |||||
/// ./ffmpeg -f dshow -i video="USB2.0 PC CAMERA" -start_number 0 -hls_list_size 0 -f hls "D:\v\sample.m3u8 -segment_time 5" | |||||
/// </summary> | |||||
class FFMPEGHLSHostedService : IHostedService | |||||
{ | |||||
private readonly Process process; | |||||
private const string FileName= "hls_ch1.m3u8"; | |||||
private const string DirectoryName = "hlsvideo"; | |||||
private readonly IWebHost webHost; | |||||
public FFMPEGHLSHostedService() | |||||
{ | |||||
string directoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DirectoryName); | |||||
if (Directory.Exists(directoryPath)) | |||||
{ | |||||
Directory.Delete(directoryPath,true); | |||||
Directory.CreateDirectory(directoryPath); | |||||
} | |||||
else | |||||
{ | |||||
Directory.CreateDirectory(directoryPath); | |||||
} | |||||
string filePath =$"\"{Path.Combine(directoryPath, FileName)}\""; | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -vcodec h264 -start_number 0 -hls_list_size 0 -f hls {filePath}", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true | |||||
} | |||||
}; | |||||
webHost= new WebHostBuilder() | |||||
.ConfigureLogging((_, factory) => | |||||
{ | |||||
factory.SetMinimumLevel(LogLevel.Debug); | |||||
factory.AddConsole(); | |||||
}) | |||||
.ConfigureAppConfiguration((hostingContext, config) => | |||||
{ | |||||
config.SetBasePath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"HLS")); | |||||
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); | |||||
}) | |||||
.UseKestrel(ksOptions => | |||||
{ | |||||
ksOptions.ListenAnyIP(5001); | |||||
}) | |||||
.UseWebRoot(AppDomain.CurrentDomain.BaseDirectory) | |||||
.UseStartup<Startup>() | |||||
.Build(); | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Start(); | |||||
webHost.RunAsync(cancellationToken); | |||||
return Task.CompletedTask; | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
webHost.WaitForShutdownAsync(); | |||||
process.Kill(); | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.StaticFiles; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.DotNetty.TestHosting.HLS | |||||
{ | |||||
public class Startup | |||||
{ | |||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) | |||||
{ | |||||
//mime | |||||
//https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/DeployingHTTPLiveStreaming/DeployingHTTPLiveStreaming.html | |||||
var Provider = new FileExtensionContentTypeProvider(); | |||||
Provider.Mappings[".m3u8"] = "application/x-mpegURL,vnd.apple.mpegURL"; | |||||
Provider.Mappings[".ts"] = "video/MP2T"; | |||||
app.UseStaticFiles(new StaticFileOptions() | |||||
{ | |||||
ContentTypeProvider = Provider | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
{ | |||||
"Logging": { | |||||
"LogLevel": { | |||||
"Default": "Debug" //Warning | |||||
} | |||||
}, | |||||
"AllowedHosts": "*", | |||||
"AllowedOrigins": "*" | |||||
} |
@@ -0,0 +1,25 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title>hls demo</title> | |||||
<script src="hls.min.js"></script> | |||||
</head> | |||||
<body> | |||||
<h6>https://poanchen.github.io/blog/2016/11/17/how-to-play-mp4-video-using-hls</h6> | |||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||||
<script type="text/javascript"> | |||||
var video = document.getElementById("player"); | |||||
var videoSrcHls = "/hlsvideo/hls_ch1.m3u8"; | |||||
if (Hls.isSupported()) { | |||||
var hls = new Hls(); | |||||
hls.loadSource(videoSrcHls); | |||||
hls.attachMedia(video); | |||||
hls.on(Hls.Events.MANIFEST_PARSED, function () { | |||||
video.play(); | |||||
}); | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,175 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using JT1078.DotNetty.Core.Session; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
using DotNetty.Common.Utilities; | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Handlers.Streams; | |||||
using DotNetty.Transport.Channels; | |||||
namespace JT1078.DotNetty.TestHosting | |||||
{ | |||||
class FFMPEGHTTPFLVHostedService : IHostedService,IDisposable | |||||
{ | |||||
private readonly Process process; | |||||
private readonly NamedPipeServerStream pipeServerOut; | |||||
private const string PipeNameOut = "demo1serverout"; | |||||
private static readonly AsciiString ServerName = AsciiString.Cached("JT1078Netty"); | |||||
private static readonly AsciiString DateEntity = HttpHeaderNames.Date; | |||||
private static readonly AsciiString ServerEntity = HttpHeaderNames.Server; | |||||
private readonly JT1078HttpSessionManager jT1078HttpSessionManager; | |||||
/// <summary> | |||||
/// 需要缓存flv的第一包数据,当新用户进来先推送第一包的数据 | |||||
/// </summary> | |||||
private byte[] flvFirstPackage; | |||||
private ConcurrentDictionary<string, byte> exists = new ConcurrentDictionary<string, byte>(); | |||||
public FFMPEGHTTPFLVHostedService(JT1078HttpSessionManager jT1078HttpSessionManager) | |||||
{ | |||||
pipeServerOut = new NamedPipeServerStream(PipeNameOut, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,10240,10240); | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -c copy -f flv -vcodec h264 -y \\.\pipe\{PipeNameOut}", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true | |||||
} | |||||
}; | |||||
this.jT1078HttpSessionManager = jT1078HttpSessionManager; | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
pipeServerOut.Dispose(); | |||||
} | |||||
public byte[] Chunk(byte[] data) | |||||
{ | |||||
byte[] buffer =new byte[4+2+2+ data.Length]; | |||||
buffer[0] = (byte)(data.Length >> 24); | |||||
buffer[1] = (byte)(data.Length >> 16); | |||||
buffer[2] = (byte)(data.Length >> 8); | |||||
buffer[3] = (byte)data.Length; | |||||
buffer[4]=(byte)'\r'; | |||||
buffer[5] = (byte)'\n'; | |||||
Array.Copy(data,0, buffer, 7,data.Length); | |||||
buffer[buffer.Length - 2] = (byte)'\r'; | |||||
buffer[buffer.Length - 1] = (byte)'\n'; | |||||
return buffer; | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
process.Start(); | |||||
Task.Run(() => | |||||
{ | |||||
while (true) | |||||
{ | |||||
try | |||||
{ | |||||
Console.WriteLine("IsConnected>>>" + pipeServerOut.IsConnected); | |||||
if (pipeServerOut.IsConnected) | |||||
{ | |||||
if (pipeServerOut.CanRead) | |||||
{ | |||||
Span<byte> v1 = new byte[2048]; | |||||
var length = pipeServerOut.Read(v1); | |||||
var realValue = v1.Slice(0, length).ToArray(); | |||||
if (realValue.Length <= 0) continue; | |||||
if (flvFirstPackage == null) | |||||
{ | |||||
flvFirstPackage = realValue; | |||||
} | |||||
if (jT1078HttpSessionManager.GetAll().Count() > 0) | |||||
{ | |||||
foreach (var session in jT1078HttpSessionManager.GetAll()) | |||||
{ | |||||
if (!exists.ContainsKey(session.Channel.Id.AsShortText())) | |||||
{ | |||||
IFullHttpResponse firstRes = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK); | |||||
firstRes.Headers.Set(ServerEntity, ServerName); | |||||
firstRes.Headers.Set(DateEntity, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); | |||||
firstRes.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, "*"); | |||||
firstRes.Headers.Set(HttpHeaderNames.AccessControlAllowMethods, "GET,POST,HEAD,PUT,DELETE,OPTIONS"); | |||||
firstRes.Headers.Set(HttpHeaderNames.AccessControlAllowCredentials, "*"); | |||||
firstRes.Headers.Set(HttpHeaderNames.AccessControlAllowHeaders, "origin,range,accept-encoding,referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type"); | |||||
firstRes.Headers.Set(HttpHeaderNames.AccessControlExposeHeaders, "Server,range,Content-Length,Content-Range"); | |||||
firstRes.Headers.Set(HttpHeaderNames.AcceptRanges, "bytes"); | |||||
firstRes.Headers.Set(HttpHeaderNames.ContentType, "video/x-flv"); | |||||
firstRes.Headers.Set(HttpHeaderNames.Connection, "Keep-Alive"); | |||||
//HttpUtil.SetContentLength(firstRes, long.MaxValue); | |||||
firstRes.Content.WriteBytes(flvFirstPackage); | |||||
session.Channel.WriteAndFlushAsync(firstRes); | |||||
exists.TryAdd(session.Channel.Id.AsShortText(), 0); | |||||
} | |||||
IFullHttpResponse res2 = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK); | |||||
res2.Headers.Set(ServerEntity, ServerName); | |||||
res2.Headers.Set(DateEntity, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); | |||||
res2.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, "*"); | |||||
res2.Headers.Set(HttpHeaderNames.AccessControlAllowMethods, "GET,POST,HEAD,PUT,DELETE,OPTIONS"); | |||||
res2.Headers.Set(HttpHeaderNames.AccessControlAllowCredentials, "*"); | |||||
res2.Headers.Set(HttpHeaderNames.AccessControlAllowHeaders, "origin,range,accept-encoding,referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type"); | |||||
res2.Headers.Set(HttpHeaderNames.AccessControlExposeHeaders, "Server,range,Content-Length,Content-Range"); | |||||
res2.Headers.Set(HttpHeaderNames.AcceptRanges, "bytes"); | |||||
res2.Headers.Set(HttpHeaderNames.ContentType, "video/x-flv"); | |||||
res2.Headers.Set(HttpHeaderNames.Connection, "Keep-Alive"); | |||||
//HttpUtil.SetContentLength(res2, long.MaxValue); | |||||
res2.Content.WriteBytes(realValue); | |||||
session.Channel.WriteAndFlushAsync(res2); | |||||
} | |||||
} | |||||
//Console.WriteLine(JsonConvert.SerializeObject(realValue)+"-"+ length.ToString()); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (!pipeServerOut.IsConnected) | |||||
{ | |||||
Console.WriteLine("WaitForConnection Star..."); | |||||
pipeServerOut.WaitForConnectionAsync(); | |||||
Console.WriteLine("WaitForConnection End..."); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
} | |||||
} | |||||
}); | |||||
return Task.CompletedTask; | |||||
} | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
try | |||||
{ | |||||
process.Kill(); | |||||
pipeServerOut.Flush(); | |||||
pipeServerOut.Close(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -1,99 +0,0 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http.WebSockets; | |||||
using JT1078.DotNetty.Core.Session; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using JT1078.Protocol; | |||||
using System.Collections.Concurrent; | |||||
using JT1078.Protocol.Enums; | |||||
using System.Diagnostics; | |||||
using System.IO.Pipes; | |||||
using Newtonsoft.Json; | |||||
namespace JT1078.DotNetty.TestHosting | |||||
{ | |||||
class FFMPEGHTTPFLVPHostedService : BackgroundService | |||||
{ | |||||
private readonly Process process; | |||||
private readonly NamedPipeServerStream pipeServerOut; | |||||
private const string PipeNameOut = "demo1serverout"; | |||||
public FFMPEGHTTPFLVPHostedService() | |||||
{ | |||||
pipeServerOut = new NamedPipeServerStream(PipeNameOut, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,10240,10240); | |||||
process = new Process | |||||
{ | |||||
StartInfo = | |||||
{ | |||||
FileName = @"C:\ffmpeg\bin\ffmpeg.exe", | |||||
Arguments = $@"-f dshow -i video={HardwareCamera.CameraName} -c copy -f flv -vcodec h264 -y \\.\pipe\{PipeNameOut}", | |||||
UseShellExecute = false, | |||||
CreateNoWindow = true, | |||||
RedirectStandardError = true | |||||
} | |||||
}; | |||||
} | |||||
public override void Dispose() | |||||
{ | |||||
try | |||||
{ | |||||
process.Close(); | |||||
pipeServerOut.Flush(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
process.Dispose(); | |||||
pipeServerOut.Dispose(); | |||||
base.Dispose(); | |||||
} | |||||
protected override Task ExecuteAsync(CancellationToken stoppingToken) | |||||
{ | |||||
process.Start(); | |||||
Task.Run(() => | |||||
{ | |||||
while (true) | |||||
{ | |||||
try | |||||
{ | |||||
Console.WriteLine("IsConnected>>>" + pipeServerOut.IsConnected); | |||||
if (pipeServerOut.IsConnected) | |||||
{ | |||||
if (pipeServerOut.CanRead) | |||||
{ | |||||
Span<byte> v1 = new byte[2048]; | |||||
var length = pipeServerOut.Read(v1); | |||||
var realValue = v1.Slice(0, length).ToArray(); | |||||
if (realValue.Length <= 0) continue; | |||||
Console.WriteLine(JsonConvert.SerializeObject(realValue)+"-"+ length.ToString()); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (!pipeServerOut.IsConnected) | |||||
{ | |||||
Console.WriteLine("WaitForConnection Star..."); | |||||
pipeServerOut.WaitForConnectionAsync(); | |||||
Console.WriteLine("WaitForConnection End..."); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
} | |||||
} | |||||
}); | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title></title> | |||||
<script src="flv.min.js"></script> | |||||
</head> | |||||
<body> | |||||
<video muted="muted" webkit-playsinline="true" autoplay="true" id="player"></video> | |||||
<script> | |||||
if (flvjs.isSupported()) { | |||||
var player = document.getElementById('player'); | |||||
var flvPlayer = flvjs.createPlayer({ | |||||
type: 'flv', | |||||
isLive: true, | |||||
url: "http://127.0.0.1:1819/demo.flv?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
}); | |||||
flvPlayer.attachMediaElement(player); | |||||
flvPlayer.load(); | |||||
flvPlayer.play(); | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -6,6 +6,10 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" /> | <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" /> | ||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" 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.Hosting" Version="2.2.0" /> | ||||
@@ -15,9 +19,9 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\JT1078.DotNetty.Http\JT1078.DotNetty.Http.csproj" /> | |||||
<ProjectReference Include="..\JT1078.DotNetty.Tcp\JT1078.DotNetty.Tcp.csproj" /> | <ProjectReference Include="..\JT1078.DotNetty.Tcp\JT1078.DotNetty.Tcp.csproj" /> | ||||
<ProjectReference Include="..\JT1078.DotNetty.Udp\JT1078.DotNetty.Udp.csproj" /> | <ProjectReference Include="..\JT1078.DotNetty.Udp\JT1078.DotNetty.Udp.csproj" /> | ||||
<ProjectReference Include="..\JT1078.DotNetty.WebSocket\JT1078.DotNetty.WebSocket.csproj" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -36,6 +40,21 @@ | |||||
<None Update="Configs\NLog.xsd"> | <None Update="Configs\NLog.xsd"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
<None Update="HLS\appsettings.json"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="HLS\hls.html"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="HLS\hls.min.js"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="HTTPFLV\flv.html"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="HTTPFLV\flv.min.js"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -2,7 +2,7 @@ | |||||
using JT1078.DotNetty.Tcp; | using JT1078.DotNetty.Tcp; | ||||
using JT1078.DotNetty.TestHosting.Handlers; | using JT1078.DotNetty.TestHosting.Handlers; | ||||
using JT1078.DotNetty.Udp; | using JT1078.DotNetty.Udp; | ||||
using JT1078.DotNetty.WebSocket; | |||||
using JT1078.DotNetty.Http; | |||||
using Microsoft.Extensions.Configuration; | using Microsoft.Extensions.Configuration; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
using Microsoft.Extensions.DependencyInjection.Extensions; | using Microsoft.Extensions.DependencyInjection.Extensions; | ||||
@@ -64,14 +64,17 @@ namespace JT1078.DotNetty.TestHosting | |||||
// .AddJT1078UdpHost() | // .AddJT1078UdpHost() | ||||
// .Replace<JT1078UdpMessageHandlers>() | // .Replace<JT1078UdpMessageHandlers>() | ||||
// .Builder() | // .Builder() | ||||
.AddJT1078WebSocketHost() | |||||
.Builder(); | |||||
//1.success 7-8s | |||||
//.AddJT1078HttpHost() | |||||
//.Builder(); | |||||
; | |||||
//1.success | |||||
//services.AddHostedService<FFMPEGRTMPHostedService>(); | //services.AddHostedService<FFMPEGRTMPHostedService>(); | ||||
//2.test | //2.test | ||||
//services.AddHostedService<FFMPEGHTTPFLVPHostedService>(); | |||||
//3.success 6-7s | |||||
//services.AddHostedService<FFMPEGHTTPFLVHostedService>(); | |||||
//3.success | |||||
//services.AddHostedService<FFMPEGWSFLVPHostedService>(); | //services.AddHostedService<FFMPEGWSFLVPHostedService>(); | ||||
//4.success | |||||
services.AddHostedService<FFMPEGHLSHostedService>(); | |||||
}); | }); | ||||
await serverHostBuilder.RunConsoleAsync(); | await serverHostBuilder.RunConsoleAsync(); | ||||
@@ -27,7 +27,7 @@ namespace JT1078.DotNetty.TestHosting | |||||
/// ffmpeg pipe作为客户端 | /// ffmpeg pipe作为客户端 | ||||
/// NamedPipeServerStream作为服务端 | /// NamedPipeServerStream作为服务端 | ||||
/// </summary> | /// </summary> | ||||
class FFMPEGRTMPHostedService : BackgroundService | |||||
class FFMPEGRTMPHostedService : IHostedService | |||||
{ | { | ||||
private readonly Process process; | private readonly Process process; | ||||
public FFMPEGRTMPHostedService() | public FFMPEGRTMPHostedService() | ||||
@@ -44,15 +44,15 @@ namespace JT1078.DotNetty.TestHosting | |||||
}; | }; | ||||
} | } | ||||
public override void Dispose() | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | { | ||||
process.Dispose(); | |||||
base.Dispose(); | |||||
process.Start(); | |||||
return Task.CompletedTask; | |||||
} | } | ||||
protected override Task ExecuteAsync(CancellationToken stoppingToken) | |||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | { | ||||
process.Start(); | |||||
process.Kill(); | |||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
} | } | ||||
@@ -21,19 +21,19 @@ namespace JT1078.DotNetty.TestHosting | |||||
/// <summary> | /// <summary> | ||||
/// | /// | ||||
/// </summary> | /// </summary> | ||||
class FFMPEGWSFLVPHostedService :BackgroundService | |||||
class FFMPEGWSFLVPHostedService : IHostedService,IDisposable | |||||
{ | { | ||||
private readonly Process process; | private readonly Process process; | ||||
private readonly NamedPipeServerStream pipeServerOut; | private readonly NamedPipeServerStream pipeServerOut; | ||||
private const string PipeNameOut = "demo2serverout"; | private const string PipeNameOut = "demo2serverout"; | ||||
private readonly JT1078WebSocketSessionManager jT1078WebSocketSessionManager; | |||||
private readonly JT1078HttpSessionManager jT1078HttpSessionManager; | |||||
/// <summary> | /// <summary> | ||||
/// 需要缓存flv的第一包数据,当新用户进来先推送第一包的数据 | /// 需要缓存flv的第一包数据,当新用户进来先推送第一包的数据 | ||||
/// </summary> | /// </summary> | ||||
private byte[] flvFirstPackage; | private byte[] flvFirstPackage; | ||||
private ConcurrentDictionary<string,byte> exists = new ConcurrentDictionary<string, byte>(); | private ConcurrentDictionary<string,byte> exists = new ConcurrentDictionary<string, byte>(); | ||||
public FFMPEGWSFLVPHostedService( | public FFMPEGWSFLVPHostedService( | ||||
JT1078WebSocketSessionManager jT1078WebSocketSessionManager) | |||||
JT1078HttpSessionManager jT1078HttpSessionManager) | |||||
{ | { | ||||
pipeServerOut = new NamedPipeServerStream(PipeNameOut, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,102400,102400); | pipeServerOut = new NamedPipeServerStream(PipeNameOut, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,102400,102400); | ||||
process = new Process | process = new Process | ||||
@@ -46,26 +46,15 @@ namespace JT1078.DotNetty.TestHosting | |||||
CreateNoWindow = true, | CreateNoWindow = true, | ||||
} | } | ||||
}; | }; | ||||
this.jT1078WebSocketSessionManager = jT1078WebSocketSessionManager; | |||||
this.jT1078HttpSessionManager = jT1078HttpSessionManager; | |||||
} | } | ||||
public override void Dispose() | |||||
public void Dispose() | |||||
{ | { | ||||
try | |||||
{ | |||||
process.Close(); | |||||
pipeServerOut.Flush(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
process.Dispose(); | |||||
pipeServerOut.Dispose(); | pipeServerOut.Dispose(); | ||||
base.Dispose(); | |||||
} | } | ||||
protected override Task ExecuteAsync(CancellationToken stoppingToken) | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | { | ||||
process.Start(); | process.Start(); | ||||
Task.Run(() => | Task.Run(() => | ||||
@@ -87,16 +76,16 @@ namespace JT1078.DotNetty.TestHosting | |||||
{ | { | ||||
flvFirstPackage = realValue; | flvFirstPackage = realValue; | ||||
} | } | ||||
if (jT1078WebSocketSessionManager.GetAll().Count() > 0) | |||||
if (jT1078HttpSessionManager.GetAll().Count() > 0) | |||||
{ | { | ||||
foreach (var session in jT1078WebSocketSessionManager.GetAll()) | |||||
foreach (var session in jT1078HttpSessionManager.GetAll()) | |||||
{ | { | ||||
if (!exists.ContainsKey(session.Channel.Id.AsShortText())) | if (!exists.ContainsKey(session.Channel.Id.AsShortText())) | ||||
{ | { | ||||
session.Channel.WriteAndFlushAsync(new BinaryWebSocketFrame(Unpooled.WrappedBuffer(flvFirstPackage))); | session.Channel.WriteAndFlushAsync(new BinaryWebSocketFrame(Unpooled.WrappedBuffer(flvFirstPackage))); | ||||
exists.TryAdd(session.Channel.Id.AsShortText(), 0); | exists.TryAdd(session.Channel.Id.AsShortText(), 0); | ||||
} | } | ||||
session.Channel.WriteAndFlushAsync(new BinaryWebSocketFrame(Unpooled.WrappedBuffer(realValue))); | |||||
session.Channel.WriteAndFlushAsync(new BinaryWebSocketFrame(Unpooled.WrappedBuffer(realValue))); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -119,5 +108,21 @@ namespace JT1078.DotNetty.TestHosting | |||||
}); | }); | ||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
public Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
try | |||||
{ | |||||
process.Kill(); | |||||
pipeServerOut.Flush(); | |||||
pipeServerOut.Close(); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | } | ||||
} | } |
@@ -15,7 +15,7 @@ | |||||
var flvPlayer = flvjs.createPlayer({ | var flvPlayer = flvjs.createPlayer({ | ||||
type: 'flv', | type: 'flv', | ||||
isLive: true, | isLive: true, | ||||
url: "ws://127.0.0.1:1818?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
url: "ws://127.0.0.1:1818/jt1078live?token=" + Math.floor((Math.random() * 1000000) + 1) | |||||
}); | }); | ||||
flvPlayer.attachMediaElement(player); | flvPlayer.attachMediaElement(player); | ||||
flvPlayer.load(); | flvPlayer.load(); | ||||
@@ -16,6 +16,7 @@ | |||||
"TcpPort": 1808, | "TcpPort": 1808, | ||||
"UdpPort": 1808, | "UdpPort": 1808, | ||||
"WebSocketPort": 1818, | "WebSocketPort": 1818, | ||||
"HttpPort": 1819, | |||||
"RemoteServerOptions": { | "RemoteServerOptions": { | ||||
} | } | ||||
@@ -1,30 +0,0 @@ | |||||
using JT1078.DotNetty.Core.Interfaces; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.DotNetty.WebSocket | |||||
{ | |||||
class JT1078WebSocketBuilderDefault : IJT1078WebSocketBuilder | |||||
{ | |||||
public IJT1078Builder Instance { get; } | |||||
public JT1078WebSocketBuilderDefault(IJT1078Builder builder) | |||||
{ | |||||
Instance = builder; | |||||
} | |||||
public IJT1078Builder Builder() | |||||
{ | |||||
return Instance; | |||||
} | |||||
public IJT1078WebSocketBuilder Replace<T>() where T : IJT1078Authorization | |||||
{ | |||||
Instance.Services.Replace(new ServiceDescriptor(typeof(IJT1078Authorization), typeof(T), ServiceLifetime.Singleton)); | |||||
return this; | |||||
} | |||||
} | |||||
} |
@@ -1,25 +0,0 @@ | |||||
using JT1078.DotNetty.Core.Codecs; | |||||
using JT1078.DotNetty.Core.Impl; | |||||
using JT1078.DotNetty.Core.Interfaces; | |||||
using JT1078.DotNetty.Core.Session; | |||||
using JT1078.DotNetty.WebSocket.Authorization; | |||||
using JT1078.DotNetty.WebSocket.Handlers; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using System.Runtime.CompilerServices; | |||||
namespace JT1078.DotNetty.WebSocket | |||||
{ | |||||
public static class JT1078WebSocketDotnettyExtensions | |||||
{ | |||||
public static IJT1078WebSocketBuilder AddJT1078WebSocketHost(this IJT1078Builder builder) | |||||
{ | |||||
builder.Services.TryAddSingleton<JT1078WebSocketSessionManager>(); | |||||
builder.Services.TryAddSingleton<IJT1078Authorization,JT1078AuthorizationDefault>(); | |||||
builder.Services.AddScoped<JT1078WebSocketServerHandler>(); | |||||
builder.Services.AddHostedService<JT1078WebSocketServerHost>(); | |||||
return new JT1078WebSocketBuilderDefault(builder); | |||||
} | |||||
} | |||||
} |
@@ -1,13 +0,0 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<!-- | |||||
https://go.microsoft.com/fwlink/?LinkID=208121. | |||||
--> | |||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
<PropertyGroup> | |||||
<PublishProtocol>FileSystem</PublishProtocol> | |||||
<Configuration>Release</Configuration> | |||||
<Platform>Any CPU</Platform> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
<PublishDir>..\..\publish\</PublishDir> | |||||
</PropertyGroup> | |||||
</Project> |
@@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.DotNetty.Udp", "JT10 | |||||
EndProject | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1C26DF6A-2978-46B7-B921-BB7776CC6EE8}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1C26DF6A-2978-46B7-B921-BB7776CC6EE8}" | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.DotNetty.WebSocket", "JT1078.DotNetty.WebSocket\JT1078.DotNetty.WebSocket.csproj", "{55181194-5AED-4C4B-8501-C8A17A3587B1}" | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.DotNetty.Http", "JT1078.DotNetty.Http\JT1078.DotNetty.Http.csproj", "{C6B9DB90-8A6C-4285-A03F-C03E2E8DF7CC}" | |||||
EndProject | EndProject | ||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
@@ -37,10 +37,10 @@ Global | |||||
{6405D7FA-3B6E-4545-827E-BA13EB5BB268}.Debug|Any CPU.Build.0 = Debug|Any CPU | {6405D7FA-3B6E-4545-827E-BA13EB5BB268}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{6405D7FA-3B6E-4545-827E-BA13EB5BB268}.Release|Any CPU.ActiveCfg = Release|Any CPU | {6405D7FA-3B6E-4545-827E-BA13EB5BB268}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{6405D7FA-3B6E-4545-827E-BA13EB5BB268}.Release|Any CPU.Build.0 = Release|Any CPU | {6405D7FA-3B6E-4545-827E-BA13EB5BB268}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{55181194-5AED-4C4B-8501-C8A17A3587B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{55181194-5AED-4C4B-8501-C8A17A3587B1}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{55181194-5AED-4C4B-8501-C8A17A3587B1}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{55181194-5AED-4C4B-8501-C8A17A3587B1}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{C6B9DB90-8A6C-4285-A03F-C03E2E8DF7CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{C6B9DB90-8A6C-4285-A03F-C03E2E8DF7CC}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{C6B9DB90-8A6C-4285-A03F-C03E2E8DF7CC}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{C6B9DB90-8A6C-4285-A03F-C03E2E8DF7CC}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||