├── nuget.bat ├── src ├── JT1078.Gateway.Tests │ ├── JT1078.Gateway.TestNormalHosting │ │ ├── wwwroot │ │ │ ├── hls_demo │ │ │ │ ├── demo.ts │ │ │ │ ├── 10.ts │ │ │ │ ├── 11.ts │ │ │ │ ├── 12.ts │ │ │ │ ├── 13.ts │ │ │ │ ├── 14.ts │ │ │ │ ├── 15.ts │ │ │ │ ├── 16.ts │ │ │ │ ├── 17.ts │ │ │ │ ├── 7.ts │ │ │ │ ├── 8.ts │ │ │ │ ├── 9.ts │ │ │ │ ├── demo.m3u8 │ │ │ │ ├── demo0.ts │ │ │ │ ├── live.m3u8 │ │ │ │ └── index.html │ │ │ ├── .vscode │ │ │ │ └── settings.json │ │ │ ├── flv_demo │ │ │ │ ├── .vscode │ │ │ │ │ └── settings.json │ │ │ │ └── index.html │ │ │ └── fmp4_demo │ │ │ │ └── index.html │ │ ├── startup.ini │ │ ├── appsettings.json │ │ ├── Services │ │ │ ├── MessageDispatchDataService.cs │ │ │ ├── MessageDispatchHostedService.cs │ │ │ ├── JT1078HlsNormalMsgHostedService.cs │ │ │ ├── JT1078FlvNormalMsgHostedService.cs │ │ │ └── JT1078FMp4NormalMsgHostedService.cs │ │ ├── JT1078.Gateway.TestNormalHosting.csproj │ │ ├── Configs │ │ │ ├── nlog.Win32NT.config │ │ │ └── nlog.Unix.config │ │ └── Program.cs │ └── JT1078.Gateway.Test │ │ ├── PipeTest.cs │ │ └── JT1078.Gateway.Test.csproj ├── Version.props ├── JT1078.Gateway.InMemoryMQ │ ├── JT1078.Gateway.InMemoryMQ.xml │ ├── JT1078MsgChannel.cs │ ├── JT1078.Gateway.InMemoryMQ.csproj │ ├── JT1078MsgProducer.cs │ ├── JT1078InMemoryMQExtensions.cs │ └── JT1078MsgConsumer.cs ├── JT1078.Gateway.Coordinator │ ├── appsettings.Development.json │ ├── JT1078.Gateway.Coordinator.csproj │ ├── appsettings.json │ ├── Dtos │ │ ├── LoginRequest.cs │ │ ├── ChannelCloseRequest.cs │ │ └── HeartbeatRequest.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Controller │ │ ├── UserController.cs │ │ └── CoordinatorController.cs │ └── Program.cs ├── JT1078.Gateway.Abstractions │ ├── IJT1078PubSub.cs │ ├── JT1078GatewayConstants.cs │ ├── IJT1078Builder.cs │ ├── Enums │ │ └── JT1078TransportProtocolType.cs │ ├── IJT1078Authorization.cs │ ├── IJT1078GatewayBuilder.cs │ ├── IJT1078MsgConsumer.cs │ ├── IJT1078MsgProducer.cs │ ├── IJT1078Session.cs │ ├── JT1078.Gateway.Abstractions.xml │ └── JT1078.Gateway.Abstractions.csproj ├── JT1078.Gateway │ ├── Impl │ │ ├── JT1078BuilderDefault.cs │ │ ├── JT1078GatewayBuilderDefault.cs │ │ └── JT1078AuthorizationDefault.cs │ ├── Services │ │ ├── JT1078SessionNoticeService.cs │ │ ├── HLSPathStorage.cs │ │ └── HLSRequestManager.cs │ ├── Metadata │ │ ├── JT1078AVInfo.cs │ │ └── JT1078HttpContext.cs │ ├── Sessions │ │ ├── JT1078UdpSession.cs │ │ ├── JT1078TcpSession.cs │ │ ├── JT1078HttpSessionManager.cs │ │ └── JT1078SessionManager.cs │ ├── JT1078.Gateway.csproj │ ├── Configurations │ │ └── JT1078Configuration.cs │ ├── Jobs │ │ ├── JT1078SessionNoticeJob.cs │ │ ├── JT1078TcpReceiveTimeoutJob.cs │ │ ├── JT1078UdpReceiveTimeoutJob.cs │ │ ├── JT1078SessionClearJob.cs │ │ └── JT1078HeartbeatJob.cs │ ├── JT1078CoordinatorHttpClient.cs │ ├── JT1078GatewayExtensions.cs │ ├── JT1078UdpServer.cs │ ├── Extensions │ │ └── JT1078HttpContextExtensions.cs │ ├── JT1078HttpServer.cs │ ├── JT1078.Gateway.xml │ └── JT1078TcpServer.cs ├── Info.props └── JT1078.Gateway.sln ├── publish.bat ├── LICENSE ├── README.md └── .gitignore /nuget.bat: -------------------------------------------------------------------------------- 1 | dotnet nuget push .\nupkgs\*.nupkg -k apikey -s https://api.nuget.org/v3/index.json -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/demo.ts: -------------------------------------------------------------------------------- 1 | ts demo -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/flv_demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /src/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0-preview2 4 | 5 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.Test/PipeTest.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.Test/PipeTest.cs -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/10.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/10.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/11.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/11.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/12.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/12.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/13.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/13.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/14.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/14.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/15.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/15.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/16.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/16.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/17.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/17.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/7.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/7.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/8.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/8.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/9.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/9.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/demo.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:3 3 | #EXT-X-TARGETDURATION:7 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXTINF:7.200667, 6 | demo0.ts 7 | #EXT-X-ENDLIST 8 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/demo0.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallChi/JT1078Gateway/HEAD/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/demo0.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.InMemoryMQ/JT1078.Gateway.InMemoryMQ.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JT1078.Gateway.InMemoryMQ 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/JT1078.Gateway.Coordinator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | true 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078PubSub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JT1078.Gateway.Abstractions 6 | { 7 | public interface IJT1078PubSub 8 | { 9 | string TopicName { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/JT1078GatewayConstants.cs: -------------------------------------------------------------------------------- 1 | namespace JT1078.Gateway.Abstractions 2 | { 3 | public static class JT1078GatewayConstants 4 | { 5 | public const string SessionOnline= "JT1078SessionOnline"; 6 | public const string SessionOffline = "JT1078SessionOffline"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /publish.bat: -------------------------------------------------------------------------------- 1 | dotnet pack .\src\JT1078.Gateway\JT1078.Gateway.csproj --no-build --output .\nupkgs 2 | dotnet pack .\src\JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj --no-build --output .\nupkgs 3 | dotnet pack .\src\JT1078.Gateway.InMemoryMQ\JT1078.Gateway.InMemoryMQ.csproj --no-build --output .\nupkgs 4 | 5 | pause -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078Builder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace JT1078.Gateway.Abstractions 7 | { 8 | public interface IJT1078Builder 9 | { 10 | IServiceCollection Services { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Dtos/LoginRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace JT1078.Gateway.Coordinator.Dtos 7 | { 8 | public class LoginRequest 9 | { 10 | public string UserName { get; set; } 11 | public string Password { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/Enums/JT1078TransportProtocolType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JT1078.Gateway.Abstractions.Enums 6 | { 7 | /// 8 | /// 传输协议类型 9 | /// 10 | public enum JT1078TransportProtocolType 11 | { 12 | tcp = 1, 13 | udp = 2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078Authorization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Security.Principal; 5 | using System.Text; 6 | 7 | namespace JT1078.Gateway.Abstractions 8 | { 9 | public interface IJT1078Authorization 10 | { 11 | bool Authorization(HttpListenerContext context, out IPrincipal principal); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "JT1078.Gateway.Coordinator": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "commandLineArgs": "Development", 7 | "applicationUrl": "http://localhost:1080", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078GatewayBuilder.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Protocol; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace JT1078.Gateway.Abstractions 8 | { 9 | public interface IJT1078GatewayBuilder 10 | { 11 | IJT1078Builder JT1078Builder { get; } 12 | IJT1078Builder Builder(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Impl/JT1078BuilderDefault.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace JT1078.Gateway.Impl 5 | { 6 | sealed class JT1078BuilderDefault : IJT1078Builder 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | public JT1078BuilderDefault(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078MsgConsumer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | 6 | namespace JT1078.Gateway.Abstractions 7 | { 8 | public interface IJT1078MsgConsumer : IJT1078PubSub, IDisposable 9 | { 10 | void OnMessage(Action<(string SIM, byte[] Data)> callback); 11 | CancellationTokenSource Cts { get; } 12 | void Subscribe(); 13 | void Unsubscribe(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/live.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:3 3 | #EXT-X-ALLOW-CACHE:NO 4 | #EXT-X-TARGETDURATION:10 5 | #EXT-X-MEDIA-SEQUENCE:7 6 | 7 | #EXTINF:10.04, 8 | 7.ts 9 | #EXTINF:10.041, 10 | 8.ts 11 | #EXTINF:10.08, 12 | 9.ts 13 | #EXTINF:10.001, 14 | 10.ts 15 | #EXTINF:10.12, 16 | 11.ts 17 | #EXTINF:10.04, 18 | 12.ts 19 | #EXTINF:10.001, 20 | 13.ts 21 | #EXTINF:10.04, 22 | 14.ts 23 | #EXTINF:10.04, 24 | 15.ts 25 | #EXTINF:10.041, 26 | 16.ts -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Dtos/ChannelCloseRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace JT1078.Gateway.Coordinator.Dtos 7 | { 8 | public class ChannelCloseRequest 9 | { 10 | /// 11 | /// 设备sim卡号 12 | /// 13 | public string Sim { get; set; } 14 | /// 15 | /// 通道号 16 | /// 17 | public int ChannelNo { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Impl/JT1078GatewayBuilderDefault.cs: -------------------------------------------------------------------------------- 1 | 2 | using JT1078.Gateway.Abstractions; 3 | 4 | namespace JT1078.Gateway.Impl 5 | { 6 | public class JT1078GatewayBuilderDefault : IJT1078GatewayBuilder 7 | { 8 | public IJT1078Builder JT1078Builder { get; } 9 | 10 | public JT1078GatewayBuilderDefault(IJT1078Builder builder) 11 | { 12 | JT1078Builder = builder; 13 | } 14 | 15 | public IJT1078Builder Builder() 16 | { 17 | return JT1078Builder; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/JT1078.Gateway.InMemoryMQ/JT1078MsgChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Channels; 5 | 6 | namespace JT1078.Gateway.InMemoryMQ 7 | { 8 | public class JT1078MsgChannel 9 | { 10 | public Channel<(string, byte[])> Channel { get;} 11 | 12 | public JT1078MsgChannel() 13 | { 14 | Channel = System.Threading.Channels.Channel.CreateUnbounded<(string, byte[])>(new UnboundedChannelOptions { 15 | SingleWriter=true, 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/startup.ini: -------------------------------------------------------------------------------- 1 | pm2 start "dotnet JT1078.Gateway.TestNormalHosting.dll ASPNETCORE_ENVIRONMENT=Production" --max-restarts=1 -n "JT1078.Gateway.TestNormalHosting" -o "/data/pm2Logs/JT1078.Gateway.TestNormalHosting/out.log" -e "/data/pm2Logs/JT1078.Gateway.TestNormalHosting/error.log" 2 | 3 | pm2 start "dotnet JT1078.Gateway.TestNormalHosting.dll ASPNETCORE_ENVIRONMENT=Development" --max-restarts=1 -n "JT1078.Gateway.TestNormalHosting.Dev" -o "/data/pm2Logs/JT1078.Gateway.TestNormalHosting.Dev/out.log" -e "/data/pm2Logs/JT1078.Gateway.TestNormalHosting.Dev/error.log" 4 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Services/JT1078SessionNoticeService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace JT1078.Gateway.Services 7 | { 8 | public class JT1078SessionNoticeService 9 | { 10 | public BlockingCollection<(string SessionType, string SIM,string ProtocolType)> SessionNoticeBlockingCollection { get;internal set; } 11 | public JT1078SessionNoticeService() 12 | { 13 | SessionNoticeBlockingCollection = new BlockingCollection<(string SessionType, string SIM, string ProtocolType)>(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "Debug": { 4 | "LogLevel": { 5 | "Default": "Information" 6 | } 7 | }, 8 | "Console": { 9 | "LogLevel": { 10 | "Default": "Information" 11 | } 12 | } 13 | }, 14 | "JT1078Configuration": { 15 | "HttpPort": 15555, 16 | "TcpPort": 1078 17 | }, 18 | "M3U8Option": { 19 | "TsPathSimParamName": "sim", 20 | "TsPathChannelParamName": "channel", 21 | "TsFileCapacity": 10, 22 | "TsFileMaxSecond": 5, 23 | "M3U8FileName": "live.m3u8", 24 | "HlsFileDirectory": "wwwroot" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078MsgProducer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace JT1078.Gateway.Abstractions 8 | { 9 | public interface IJT1078MsgProducer : IJT1078PubSub, IDisposable 10 | { 11 | /// 12 | /// 13 | /// 14 | /// 设备sim终端号 15 | /// jt1078 hex data 16 | /// cts 17 | ValueTask ProduceAsync(string sim, byte[] data, CancellationToken cancellationToken = default); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Dtos/HeartbeatRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace JT1078.Gateway.Coordinator.Dtos 7 | { 8 | public class HeartbeatRequest 9 | { 10 | public int HttpPort { get; set; } 11 | public int TcpPort { get; set; } 12 | public int UdpPort { get; set; } 13 | public int TcpSessionCount { get; set; } 14 | public int UdpSessionCount { get; set; } 15 | public int HttpSessionCount { get; set; } 16 | public int WebSocketSessionCount { get; set; } 17 | public List Sims { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Services/MessageDispatchDataService.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Protocol; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Channels; 7 | using System.Threading.Tasks; 8 | 9 | namespace JT1078.Gateway.TestNormalHosting.Services 10 | { 11 | public class MessageDispatchDataService 12 | { 13 | public Channel HlsChannel = Channel.CreateUnbounded(); 14 | public Channel FlvChannel = Channel.CreateUnbounded(); 15 | public Channel FMp4Channel = Channel.CreateUnbounded(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Controller/UserController.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Coordinator.Dtos; 2 | using Microsoft.AspNetCore.Cors; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace JT1078.Gateway.Coordinator.Controller 10 | { 11 | /// 12 | /// 用户功能 13 | /// 14 | [Route("JT1078WebApi/User")] 15 | [ApiController] 16 | [EnableCors("any")] 17 | public class UserController : ControllerBase 18 | { 19 | /// 20 | /// 登录 21 | /// 22 | [Route("Login")] 23 | [HttpPost] 24 | public void Login([FromBody] LoginRequest request) 25 | { 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/IJT1078Session.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace JT1078.Gateway.Abstractions 10 | { 11 | public interface IJT1078Session 12 | { 13 | /// 14 | /// 终端手机号 15 | /// 16 | string TerminalPhoneNo { get; set; } 17 | string SessionID { get; } 18 | Socket Client { get; set; } 19 | DateTime StartTime { get; set; } 20 | DateTime ActiveTime { get; set; } 21 | JT1078TransportProtocolType TransportProtocolType { get;} 22 | CancellationTokenSource ReceiveTimeout { get; set; } 23 | EndPoint RemoteEndPoint { get; set; } 24 | void Close(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.InMemoryMQ/JT1078.Gateway.InMemoryMQ.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JT1078.Gateway.InMemoryMQ 5 | JT1078.Gateway.InMemoryMQ 6 | 基于JT1078Gateway实现的内存队列 7 | 基于JT1078Gateway实现的内存队列 8 | JT1078.Gateway.InMemoryMQ.xml 9 | LICENSE 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.InMemoryMQ/JT1078MsgProducer.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Protocol; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Channels; 8 | using System.Threading.Tasks; 9 | 10 | namespace JT1078.Gateway.InMemoryMQ 11 | { 12 | public class JT1078MsgProducer : IJT1078MsgProducer 13 | { 14 | public string TopicName { get; }= "JT1078Package"; 15 | 16 | private JT1078MsgChannel Channel; 17 | 18 | public JT1078MsgProducer(JT1078MsgChannel channel) 19 | { 20 | Channel = channel; 21 | } 22 | 23 | public void Dispose() 24 | { 25 | 26 | } 27 | 28 | public async ValueTask ProduceAsync(string sim, byte[] data, CancellationToken cancellationToken = default) 29 | { 30 | await Channel.Channel.Writer.WriteAsync((sim, data), cancellationToken); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/JT1078.Gateway.Abstractions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JT1078.Gateway.Abstractions 5 | 6 | 7 | 8 | 9 | 传输协议类型 10 | 11 | 12 | 13 | 14 | 15 | 16 | 设备sim终端号 17 | jt1078 hex data 18 | cts 19 | 20 | 21 | 22 | 终端手机号 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.Test/JT1078.Gateway.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Metadata/JT1078AVInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JT1078.Gateway.Metadata 6 | { 7 | /// 8 | /// 音视频信息 9 | /// 10 | public struct JT1078AVInfo 11 | { 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | public JT1078AVInfo(string sim, int channelNo) 18 | { 19 | Sim = sim; 20 | ChannelNo = channelNo; 21 | } 22 | /// 23 | /// sim 24 | /// 25 | public string Sim { get; set; } 26 | /// 27 | /// 通道号 28 | /// 29 | public int ChannelNo { get; set; } 30 | /// 31 | /// key 32 | /// 33 | /// 34 | public override string ToString() 35 | { 36 | return $"{Sim}_{ChannelNo}"; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.InMemoryMQ/JT1078InMemoryMQExtensions.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace JT1078.Gateway.InMemoryMQ 9 | { 10 | public static class JT1078InMemoryMQExtensions 11 | { 12 | public static IJT1078GatewayBuilder AddMsgProducer(this IJT1078GatewayBuilder builder) 13 | { 14 | builder.JT1078Builder.Services.TryAddSingleton(); 15 | builder.JT1078Builder.Services.AddSingleton(); 16 | return builder; 17 | } 18 | 19 | public static IJT1078GatewayBuilder AddMsgConsumer(this IJT1078GatewayBuilder builder) 20 | { 21 | builder.JT1078Builder.Services.TryAddSingleton(); 22 | builder.JT1078Builder.Services.AddSingleton(); 23 | return builder; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SmallChi(Koike) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Impl/JT1078AuthorizationDefault.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Security.Claims; 6 | using System.Security.Principal; 7 | using System.Text; 8 | 9 | namespace JT1078.Gateway.Impl 10 | { 11 | class JT1078AuthorizationDefault : IJT1078Authorization 12 | { 13 | public bool Authorization(HttpListenerContext context, out IPrincipal principal) 14 | { 15 | var token = context.Request.QueryString.Get("token"); 16 | if (!string.IsNullOrEmpty(token)) 17 | { 18 | principal = new ClaimsPrincipal(new GenericIdentity(token)); 19 | return true; 20 | } 21 | else 22 | { 23 | token = context.Request.Headers.Get("token"); 24 | if (!string.IsNullOrEmpty(token)) 25 | { 26 | principal = new ClaimsPrincipal(new GenericIdentity(token)); 27 | return true; 28 | } 29 | principal = null; 30 | return false; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/hls_demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Sessions/JT1078UdpSession.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions.Enums; 2 | using JT1078.Gateway.Abstractions; 3 | using System; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Threading; 7 | 8 | namespace JT1078.Gateway.Sessions 9 | { 10 | public class JT1078UdpSession: IJT1078Session 11 | { 12 | public JT1078UdpSession(Socket socket) 13 | { 14 | ActiveTime = DateTime.Now; 15 | StartTime = DateTime.Now; 16 | SessionID = Guid.NewGuid().ToString("N"); 17 | ReceiveTimeout = new CancellationTokenSource(); 18 | Client = socket; 19 | } 20 | 21 | /// 22 | /// 终端手机号 23 | /// 24 | public string TerminalPhoneNo { get; set; } 25 | public DateTime ActiveTime { get; set; } 26 | public DateTime StartTime { get; set; } 27 | public JT1078TransportProtocolType TransportProtocolType { get; set; } = JT1078TransportProtocolType.udp; 28 | public string SessionID { get; } 29 | public Socket Client { get; set; } 30 | public CancellationTokenSource ReceiveTimeout { get; set; } 31 | public EndPoint RemoteEndPoint { get; set ; } 32 | public void Close() 33 | { 34 | 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.InMemoryMQ/JT1078MsgConsumer.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using System; 3 | using System.Text.Json; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace JT1078.Gateway.InMemoryMQ 8 | { 9 | public class JT1078MsgConsumer : IJT1078MsgConsumer 10 | { 11 | private JT1078MsgChannel Channel; 12 | 13 | public JT1078MsgConsumer(JT1078MsgChannel channel) 14 | { 15 | Channel = channel; 16 | } 17 | 18 | public CancellationTokenSource Cts { get; } = new CancellationTokenSource(); 19 | 20 | public string TopicName { get; } = "JT1078Package"; 21 | 22 | public void Dispose() 23 | { 24 | 25 | } 26 | 27 | public void OnMessage(Action<(string SIM, byte[] Data)> callback) 28 | { 29 | Task.Run(async() => 30 | { 31 | while (!Cts.IsCancellationRequested) 32 | { 33 | var reader = await Channel.Channel.Reader.ReadAsync(Cts.Token); 34 | callback(reader); 35 | } 36 | }, Cts.Token); 37 | } 38 | 39 | public void Subscribe() 40 | { 41 | 42 | } 43 | 44 | public void Unsubscribe() 45 | { 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JT1078Gateway 2 | 3 | ## 前提条件 4 | 5 | 1. [熟悉JT1078协议](https://github.com/SmallChi/JT1078) 6 | 2. 了解Http Chunked编码 7 | 3. 了解WebSocket消息推送 8 | 4. [了解flv.js](https://github.com/bilibili/flv.js) 9 | 5. [了解hls.js](https://github.com/video-dev/hls.js) 10 | 6. 了解fmp4 11 | 12 | > 注意:暂不支持音频 13 | 14 | | av | video | audio |test|request| 15 | | --- | ---| --- |---|---| 16 | | flv | 😀| ☹ |😀|http-flv、ws-flv| 17 | | m3u8 | 😀| ☹ |😀|http| 18 | | fmp4 | 😀| ☹ |😀(部分设备可用)|http-fmp4[X]、ws-fmp4[✔]| 19 | 20 | ## NuGet安装 21 | 22 | | Package Name | Version |Pre Version|Downloads| 23 | | --- | ---| --- | --- | 24 | | Install-Package JT1078.Gateway.Abstractions | ![JT1078.Gateway.Abstractions](https://img.shields.io/nuget/v/JT1078.Gateway.Abstractions.svg) | ![JT1078.Gateway.Abstractions](https://img.shields.io/nuget/vpre/JT1078.Gateway.Abstractions.svg) | ![JT1078.Gateway.Abstractions](https://img.shields.io/nuget/dt/JT1078.Gateway.Abstractions.svg) | 25 | | Install-Package JT1078.Gateway | ![JT1078.Gateway](https://img.shields.io/nuget/v/JT1078.Gateway.svg) | ![JT1078.Gateway](https://img.shields.io/nuget/vpre/JT1078.Gateway.svg)|![JT1078.Gateway](https://img.shields.io/nuget/dt/JT1078.Gateway.svg) | 26 | | Install-Package JT1078.Gateway.InMemoryMQ | ![JT1078.Gateway.InMemoryMQ](https://img.shields.io/nuget/v/JT1078.Gateway.InMemoryMQ.svg) | ![JT1078.Gateway.InMemoryMQ](https://img.shields.io/nuget/vpre/JT1078.Gateway.InMemoryMQ.svg) | ![JT1078.Gateway.InMemoryMQ](https://img.shields.io/nuget/dt/JT1078.Gateway.InMemoryMQ.svg) | 27 | -------------------------------------------------------------------------------- /src/Info.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | 10.0 5 | Copyright 2019. 6 | SmallChi(Koike) 7 | https://github.com/SmallChi/JT1078Gateway 8 | https://github.com/SmallChi/JT1078Gateway 9 | https://github.com/SmallChi/JT1078Gateway/blob/master/LICENSE 10 | https://github.com/SmallChi/JT1078Gateway/blob/master/LICENSE 11 | 1.0.0-preview3 12 | LICENSE 13 | true 14 | latest 15 | true 16 | true 17 | false 18 | README.md 19 | true 20 | true 21 | embedded 22 | 23 | 24 | 25 | true 26 | true 27 | true 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Sessions/JT1078TcpSession.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions.Enums; 2 | using JT1078.Gateway.Abstractions; 3 | using System; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Threading; 7 | 8 | namespace JT1078.Gateway.Sessions 9 | { 10 | public class JT1078TcpSession : IJT1078Session 11 | { 12 | public JT1078TcpSession(Socket client) 13 | { 14 | Client = client; 15 | RemoteEndPoint = client.RemoteEndPoint; 16 | ActiveTime = DateTime.Now; 17 | StartTime = DateTime.Now; 18 | SessionID = Guid.NewGuid().ToString("N"); 19 | ReceiveTimeout = new CancellationTokenSource(); 20 | } 21 | /// 22 | /// 终端手机号 23 | /// 24 | public string TerminalPhoneNo { get; set; } 25 | public DateTime ActiveTime { get; set; } 26 | public DateTime StartTime { get; set; } 27 | public JT1078TransportProtocolType TransportProtocolType { get;} = JT1078TransportProtocolType.tcp; 28 | public string SessionID { get; } 29 | public Socket Client { get; set; } 30 | public CancellationTokenSource ReceiveTimeout { get; set; } 31 | public EndPoint RemoteEndPoint { get ; set; } 32 | public void Close() 33 | { 34 | try 35 | { 36 | Client.Shutdown(SocketShutdown.Both); 37 | } 38 | catch { } 39 | finally 40 | { 41 | Client.Close(); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Controller/CoordinatorController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Threading.Tasks; 6 | using JT1078.Gateway.Coordinator.Dtos; 7 | using Microsoft.AspNetCore.Cors; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace JT1078.Gateway.Coordinator.Controller 12 | { 13 | /// 14 | /// 协调器中心 15 | /// 16 | [Route("JT1078WebApi/Coordinator")] 17 | [ApiController] 18 | [EnableCors("any")] 19 | public class CoordinatorController:ControllerBase 20 | { 21 | private ILogger logger; 22 | public CoordinatorController(ILoggerFactory loggerFactory) 23 | { 24 | logger = loggerFactory.CreateLogger(); 25 | } 26 | 27 | /// 28 | /// 集群服务器重置 29 | /// 30 | [Route("Reset")] 31 | [HttpPost] 32 | public void Reset() 33 | { 34 | 35 | } 36 | 37 | /// 38 | /// 心跳检测 39 | /// 40 | [Route("Heartbeat")] 41 | [HttpPost] 42 | public void Heartbeat([FromBody] HeartbeatRequest request) 43 | { 44 | 45 | } 46 | 47 | /// 48 | /// 关闭通道 49 | /// 50 | [Route("ChannelClose")] 51 | [HttpPost] 52 | public void ChannelClose([FromBody] ChannelCloseRequest request) 53 | { 54 | 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/flv_demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 40 | 41 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Abstractions/JT1078.Gateway.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JT1078.Gateway.Abstractions 5 | JT1078.Gateway.Abstractions 6 | 基于JT1078Gateway的抽象库 7 | 基于JT1078Gateway的抽象库 8 | JT1078.Gateway.Abstractions.xml 9 | LICENSE 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/JT1078.Gateway.TestNormalHosting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | Always 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078.Gateway.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JT1078.Gateway 5 | JT1078.Gateway 6 | 基于Pipeline实现的JT1078Gateway库 7 | 基于Pipeline实现的JT1078Gateway库 8 | JT1078.Gateway.xml 9 | LICENSE 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Configurations/JT1078Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace JT1078.Gateway.Configurations 7 | { 8 | public class JT1078Configuration : IOptions 9 | { 10 | public int TcpPort { get; set; } = 1078; 11 | public int UdpPort { get; set; } = 1078; 12 | public int HttpPort { get; set; } = 1079; 13 | public int SoBacklog { get; set; } = 8192; 14 | public int MiniNumBufferSize { get; set; } = 8096; 15 | /// 16 | /// Tcp读超时 17 | /// 默认10分钟检查一次 18 | /// 19 | public int TcpReaderIdleTimeSeconds { get; set; } = 60 * 10; 20 | /// 21 | /// Tcp 60s检查一次 22 | /// 23 | public int TcpReceiveTimeoutCheckTimeSeconds { get; set; } = 60; 24 | /// 25 | /// Udp读超时 26 | /// 27 | public int UdpReaderIdleTimeSeconds { get; set; } = 60; 28 | /// 29 | /// Udp 60s检查一次 30 | /// 31 | public int UdpReceiveTimeoutCheckTimeSeconds { get; set; } = 60; 32 | /// 33 | /// Hls根目录 34 | /// 35 | public string HlsRootDirectory { get; set; } = "wwwroot"; 36 | /// 37 | /// 协调器发送心跳时间 38 | /// 默认60s发送一次 39 | /// 40 | public int CoordinatorHeartbeatTimeSeconds { get; set; } = 60; 41 | /// 42 | /// 协调器Coordinator主机 43 | /// http://localhost/ 44 | /// http://127.0.0.1/ 45 | /// 46 | public string CoordinatorUri { get; set; } = "http://localhost:1080/"; 47 | /// 48 | /// 协调器Coordinator主机登录账号 49 | /// 50 | public string CoordinatorUserName { get; set; } = "admin"; 51 | /// 52 | /// 协调器Coordinator主机登录密码 53 | /// 54 | public string CoordinatorPassword { get; set; } = "123456"; 55 | public JT1078Configuration Value => this; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Services/MessageDispatchHostedService.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Protocol; 3 | using Microsoft.Extensions.Hosting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Channels; 10 | using System.Threading.Tasks; 11 | 12 | namespace JT1078.Gateway.TestNormalHosting.Services 13 | { 14 | /// 15 | /// 消费分发服务。同时分发给hls和flv 16 | /// 17 | public class MessageDispatchHostedService : BackgroundService 18 | { 19 | private IJT1078MsgConsumer JT1078MsgConsumer; 20 | private readonly MessageDispatchDataService messageDispatchDataService; 21 | 22 | public MessageDispatchHostedService(IJT1078MsgConsumer JT1078MsgConsumer, 23 | MessageDispatchDataService messageDispatchDataService) { 24 | this.JT1078MsgConsumer = JT1078MsgConsumer; 25 | this.messageDispatchDataService = messageDispatchDataService; 26 | } 27 | 28 | protected override Task ExecuteAsync(CancellationToken stoppingToken) 29 | { 30 | JT1078MsgConsumer.OnMessage(async (Message) => 31 | { 32 | JT1078Package package = JT1078Serializer.Deserialize(Message.Data); 33 | var merge = JT1078.Protocol.JT1078Serializer.Merge(package); 34 | if (merge != null) 35 | { 36 | await messageDispatchDataService.FMp4Channel.Writer.WriteAsync(merge, stoppingToken); 37 | //Parallel.Invoke( 38 | // async() => { 39 | // await messageDispatchDataService.FMp4Channel.Writer.WriteAsync(merge, stoppingToken); 40 | // }, 41 | // async () => { 42 | // await messageDispatchDataService.FlvChannel.Writer.WriteAsync(merge, stoppingToken); 43 | // }, 44 | // async () => { 45 | // await messageDispatchDataService.HlsChannel.Writer.WriteAsync(merge, stoppingToken); 46 | // }); 47 | } 48 | }); 49 | return Task.CompletedTask; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Jobs/JT1078SessionNoticeJob.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Services; 3 | using JT1078.Gateway.Sessions; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.Text; 10 | using System.Text.Json; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | 15 | namespace JT1078.Gateway.Jobs 16 | { 17 | public class JT1078SessionNoticeJob : BackgroundService 18 | { 19 | private readonly ILogger logger; 20 | private JT1078SessionNoticeService SessionNoticeService; 21 | private JT1078HttpSessionManager HttpSessionManager; 22 | public JT1078SessionNoticeJob( 23 | JT1078SessionNoticeService sessionNoticeService, 24 | ILoggerFactory loggerFactory, 25 | [AllowNull]JT1078HttpSessionManager jT1078HttpSessionManager=null) 26 | { 27 | logger = loggerFactory.CreateLogger(); 28 | SessionNoticeService = sessionNoticeService; 29 | HttpSessionManager = jT1078HttpSessionManager; 30 | } 31 | 32 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 33 | { 34 | await Task.Run(() => { 35 | try 36 | { 37 | foreach (var notice in SessionNoticeService.SessionNoticeBlockingCollection.GetConsumingEnumerable(stoppingToken)) 38 | { 39 | if (logger.IsEnabled(LogLevel.Information)) 40 | { 41 | logger.LogInformation($"[Notice]:{notice.SIM}-{notice.ProtocolType}-{notice.SessionType}"); 42 | } 43 | if(JT1078GatewayConstants.SessionOffline== notice.SessionType) 44 | { 45 | if (HttpSessionManager != null) 46 | { 47 | //当1078设备主动断开的情况下,需要关闭所有再观看的连接 48 | HttpSessionManager.TryRemoveBySim(notice.SIM); 49 | } 50 | } 51 | } 52 | } 53 | catch(Exception ex) 54 | { 55 | logger.LogError(ex, ""); 56 | } 57 | }, stoppingToken); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Jobs/JT1078TcpReceiveTimeoutJob.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Configurations; 2 | using JT1078.Gateway.Sessions; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace JT1078.Gateway.Jobs 13 | { 14 | internal class JT1078TcpReceiveTimeoutJob : BackgroundService 15 | { 16 | private readonly ILogger Logger; 17 | 18 | private readonly JT1078SessionManager SessionManager; 19 | 20 | private readonly IOptionsMonitor Configuration; 21 | public JT1078TcpReceiveTimeoutJob( 22 | IOptionsMonitor jT1078ConfigurationAccessor, 23 | ILoggerFactory loggerFactory, 24 | JT1078SessionManager jT1078SessionManager 25 | ) 26 | { 27 | SessionManager = jT1078SessionManager; 28 | Logger = loggerFactory.CreateLogger(); 29 | Configuration = jT1078ConfigurationAccessor; 30 | } 31 | 32 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 33 | { 34 | while (!stoppingToken.IsCancellationRequested) 35 | { 36 | try 37 | { 38 | foreach (var item in SessionManager.GetTcpAll()) 39 | { 40 | if (item.ActiveTime.AddSeconds(Configuration.CurrentValue.TcpReaderIdleTimeSeconds) < DateTime.Now) 41 | { 42 | item.ReceiveTimeout.Cancel(); 43 | } 44 | } 45 | if (Logger.IsEnabled(LogLevel.Information)) 46 | { 47 | Logger.LogInformation($"[Check Receive Timeout]"); 48 | Logger.LogInformation($"[Session Online Count]:{SessionManager.TcpSessionCount}"); 49 | } 50 | } 51 | catch (Exception ex) 52 | { 53 | Logger.LogError(ex, $"[Receive Timeout]"); 54 | } 55 | finally 56 | { 57 | await Task.Delay(TimeSpan.FromSeconds(Configuration.CurrentValue.TcpReceiveTimeoutCheckTimeSeconds), stoppingToken); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Services/JT1078HlsNormalMsgHostedService.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Sessions; 3 | using JT1078.Hls; 4 | using JT1078.Protocol; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | namespace JT1078.Gateway.TestNormalHosting.Services 15 | { 16 | public class JT1078HlsNormalMsgHostedService : BackgroundService 17 | { 18 | private IJT1078MsgConsumer MsgConsumer; 19 | private JT1078HttpSessionManager HttpSessionManager; 20 | private M3U8FileManage M3U8FileManage; 21 | private MessageDispatchDataService messageDispatchDataService; 22 | private readonly ILogger logger; 23 | public JT1078HlsNormalMsgHostedService( 24 | ILoggerFactory loggerFactory, 25 | M3U8FileManage M3U8FileManage, 26 | JT1078HttpSessionManager httpSessionManager, 27 | MessageDispatchDataService messageDispatchDataService, 28 | IJT1078MsgConsumer msgConsumer) 29 | { 30 | logger = loggerFactory.CreateLogger(); 31 | MsgConsumer = msgConsumer; 32 | HttpSessionManager = httpSessionManager; 33 | this.M3U8FileManage = M3U8FileManage; 34 | this.messageDispatchDataService = messageDispatchDataService; 35 | } 36 | protected async override Task ExecuteAsync(CancellationToken stoppingToken) 37 | { 38 | while (!stoppingToken.IsCancellationRequested) 39 | { 40 | var data = await messageDispatchDataService.HlsChannel.Reader.ReadAsync(); 41 | logger.LogDebug($"设备{data.SIM},{data.LogicChannelNumber},session:{System.Text.Json.JsonSerializer.Serialize(HttpSessionManager)}"); 42 | var hasHttpSessionn = HttpSessionManager.GetAllHttpContextBySimAndChannelNo(data.SIM, data.LogicChannelNumber).Where(m => m.RTPVideoType == Metadata.RTPVideoType.Http_Hls).ToList(); 43 | if (hasHttpSessionn.Count > 0) 44 | { 45 | logger.LogDebug($"设备{data.SIM},{data.LogicChannelNumber}连上了"); 46 | M3U8FileManage.CreateTsData(data); 47 | } 48 | else 49 | { 50 | logger.LogDebug($"没有设备链接"); 51 | } 52 | } 53 | await Task.CompletedTask; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/JT1078.Gateway/Services/HLSPathStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace JT1078.Gateway.Services 9 | { 10 | /// 11 | /// hls路径是否存在处理,及文件监控处理 12 | /// 13 | public class HLSPathStorage 14 | { 15 | private readonly ConcurrentDictionary path_sim_channelDic = new ConcurrentDictionary(); 16 | private readonly ConcurrentDictionary pathFileSystemWaterDic = new ConcurrentDictionary(); 17 | /// 18 | /// 添加路径 19 | /// 20 | /// 21 | public void AddPath(string path,string key) { 22 | path_sim_channelDic.TryAdd(path, key); 23 | } 24 | /// 25 | /// 判断路径是否存在 26 | /// 27 | /// 28 | /// 29 | public bool ExsitPath(string path) { 30 | return path_sim_channelDic.TryGetValue(path, out var _); 31 | } 32 | /// 33 | /// 移除所有路径 34 | /// 35 | /// 36 | public bool RemoveAllPath(string key) { 37 | var flag = false; 38 | var paths = path_sim_channelDic.Where(m => m.Value == key).ToList(); 39 | foreach (var item in paths) 40 | { 41 | flag = path_sim_channelDic.TryRemove(item.Key,out var _); 42 | } 43 | return flag; 44 | } 45 | 46 | /// 47 | /// 是否存在文件监控 48 | /// 49 | /// 50 | /// 51 | public bool ExistFileSystemWatcher(string path) { 52 | return pathFileSystemWaterDic.TryGetValue(path, out var _); 53 | } 54 | /// 55 | /// 添加文件监控 56 | /// 57 | /// 58 | /// 59 | public void AddFileSystemWatcher(string path, FileSystemWatcher fileSystemWatcher) { 60 | pathFileSystemWaterDic.TryAdd(path, fileSystemWatcher); 61 | } 62 | /// 63 | /// 删除文件监控 64 | /// 65 | /// 66 | public bool DeleteFileSystemWatcher(string path) 67 | { 68 | return pathFileSystemWaterDic.TryRemove(path, out var _); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Jobs/JT1078UdpReceiveTimeoutJob.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Configurations; 2 | using JT1078.Gateway.Sessions; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace JT1078.Gateway.Jobs 13 | { 14 | internal class JT1078UdpReceiveTimeoutJob : BackgroundService 15 | { 16 | private readonly ILogger Logger; 17 | 18 | private readonly JT1078SessionManager SessionManager; 19 | 20 | private readonly IOptionsMonitor Configuration; 21 | public JT1078UdpReceiveTimeoutJob( 22 | IOptionsMonitor jT1078ConfigurationAccessor, 23 | ILoggerFactory loggerFactory, 24 | JT1078SessionManager jT1078SessionManager 25 | ) 26 | { 27 | SessionManager = jT1078SessionManager; 28 | Logger = loggerFactory.CreateLogger(); 29 | Configuration = jT1078ConfigurationAccessor; 30 | } 31 | 32 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 33 | { 34 | while (!stoppingToken.IsCancellationRequested) 35 | { 36 | try 37 | { 38 | List sessionIds = new List(); 39 | foreach (var item in SessionManager.GetUdpAll()) 40 | { 41 | if (item.ActiveTime.AddSeconds(Configuration.CurrentValue.UdpReaderIdleTimeSeconds) < DateTime.Now) 42 | { 43 | sessionIds.Add(item.SessionID); 44 | } 45 | } 46 | foreach (var item in sessionIds) 47 | { 48 | SessionManager.RemoveBySessionId(item); 49 | } 50 | if (Logger.IsEnabled(LogLevel.Information)) 51 | { 52 | Logger.LogInformation($"[Check Receive Timeout]"); 53 | Logger.LogInformation($"[Session Online Count]:{SessionManager.UdpSessionCount}"); 54 | } 55 | } 56 | catch (Exception ex) 57 | { 58 | Logger.LogError(ex, $"[Receive Timeout]"); 59 | } 60 | finally 61 | { 62 | await Task.Delay(TimeSpan.FromSeconds(Configuration.CurrentValue.UdpReceiveTimeoutCheckTimeSeconds), stoppingToken); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Coordinator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpOverrides; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace JT1078.Gateway.Coordinator 14 | { 15 | public class Program 16 | { 17 | public static void Main(string[] args) 18 | { 19 | Host.CreateDefaultBuilder(args) 20 | .UseEnvironment(args[0]) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.ConfigureServices(services => 24 | { 25 | services.AddCors(options => 26 | options.AddPolicy("any", builder => 27 | builder.AllowAnyOrigin() 28 | .AllowAnyMethod() 29 | .AllowAnyHeader() 30 | .SetIsOriginAllowed(o=>true))); 31 | services.AddMemoryCache(); 32 | services.AddControllers(); 33 | services.AddMvc(); 34 | }).Configure(app => 35 | { 36 | app.UseRouting(); 37 | app.UseCors("any"); 38 | app.UseEndpoints(endpoints => 39 | { 40 | endpoints.MapControllers(); 41 | }); 42 | app.UseForwardedHeaders(new ForwardedHeadersOptions 43 | { 44 | ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost 45 | }); 46 | }); 47 | }) 48 | .ConfigureAppConfiguration((hostingContext, config) => 49 | { 50 | config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); 51 | config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 52 | .AddJsonFile($"appsettings.{ hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true); 53 | }) 54 | .ConfigureServices((hostContext, services) => 55 | { 56 | services.AddSingleton(); 57 | services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); 58 | 59 | }) 60 | .Build() 61 | .Run(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078CoordinatorHttpClient.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Configurations; 2 | using Microsoft.Extensions.Options; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace JT1078.Gateway 10 | { 11 | /// 12 | /// 协调器客户端 13 | /// 14 | public class JT1078CoordinatorHttpClient 15 | { 16 | private HttpClient httpClient; 17 | 18 | private JT1078Configuration Configuration; 19 | 20 | private const string endpoint = "/JT1078WebApi"; 21 | 22 | public JT1078CoordinatorHttpClient(IOptions configurationAccessor) 23 | { 24 | Configuration = configurationAccessor.Value; 25 | this.httpClient = new HttpClient(); 26 | this.httpClient.BaseAddress = new Uri(Configuration.CoordinatorUri); 27 | this.httpClient.Timeout = TimeSpan.FromSeconds(3); 28 | Login().GetAwaiter().GetResult(); 29 | } 30 | 31 | /// 32 | /// 登录 33 | /// 34 | public async ValueTask Login() 35 | { 36 | string json = $"{{\"UserName\":\"{Configuration.CoordinatorUserName}\",\"Password\":\"{Configuration.CoordinatorPassword}\"}}"; 37 | var response = await httpClient.PostAsync($"{endpoint}/User/Login", new StringContent(json)); 38 | response.EnsureSuccessStatusCode(); 39 | var token = await response.Content.ReadAsStringAsync(); 40 | if (string.IsNullOrEmpty(token)) 41 | { 42 | throw new NullReferenceException("token is null"); 43 | } 44 | httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}"); 45 | } 46 | 47 | /// 48 | /// 发送重制至协调器中 49 | /// 50 | public async ValueTask Reset() 51 | { 52 | await httpClient.PostAsync($"{endpoint}/Coordinator/Reset", new StringContent("")); 53 | } 54 | 55 | /// 56 | /// 发送心跳至协调器中 57 | /// 58 | /// 59 | public async ValueTask Heartbeat(string content) 60 | { 61 | await httpClient.PostAsync($"{endpoint}/Coordinator/Heartbeat", new StringContent(content)); 62 | } 63 | 64 | /// 65 | /// 发送设备号和通道给协调器中 66 | /// 67 | /// 68 | /// 69 | public async ValueTask ChannelClose(string terminalPhoneNo, int channelNo) 70 | { 71 | //todo:通过自维护,当协调重启导致集群内网关未关闭的情况下,通过轮询的方式再去调用 72 | string json = $"{{\"TerminalPhoneNo\":\"{terminalPhoneNo}\",\"ChannelNo\":\"{channelNo}\"}}"; 73 | await httpClient.PostAsync($"{endpoint}/Coordinator/ChannelClose", new StringContent(json)); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Configs/nlog.Win32NT.config: -------------------------------------------------------------------------------- 1 | 2 | 13 | 18 | 19 | 20 | 23 | 26 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Metadata/JT1078HttpContext.cs: -------------------------------------------------------------------------------- 1 | using JT1078.FMp4; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.WebSockets; 6 | using System.Security.Principal; 7 | using System.Text; 8 | using System.Text.Json.Serialization; 9 | 10 | namespace JT1078.Gateway.Metadata 11 | { 12 | /// 13 | /// http上下文 14 | /// 15 | public class JT1078HttpContext 16 | { 17 | /// 18 | /// 会话Id 19 | /// 20 | public string SessionId { get; } 21 | /// 22 | /// http上下文 23 | /// 24 | [JsonIgnore] 25 | public HttpListenerContext Context { get; } 26 | /// 27 | /// ws上下文 28 | /// 29 | [JsonIgnore] 30 | public HttpListenerWebSocketContext WebSocketContext { get; } 31 | /// 32 | /// 用户信息 33 | /// 34 | public IPrincipal User { get; } 35 | /// 36 | /// 观看视频类型 37 | /// 38 | public RTPVideoType RTPVideoType { get; set; } 39 | public string Sim { get; set; } 40 | public int ChannelNo { get; set; } 41 | /// 42 | /// 是否是ws协议 43 | /// 44 | public bool IsWebSocket 45 | { 46 | get 47 | { 48 | return Context.Request.IsWebSocketRequest; 49 | } 50 | } 51 | /// 52 | /// 开始时间 53 | /// 54 | public DateTime StartTime { get; set; } 55 | /// 56 | /// 是否发送首包视频数据 57 | /// 58 | public bool FirstSend { get; set; } 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | public JT1078HttpContext(HttpListenerContext context, IPrincipal user) 65 | { 66 | Context = context; 67 | User = user; 68 | StartTime = DateTime.Now; 69 | SessionId = Guid.NewGuid().ToString("N"); 70 | FirstSend = false; 71 | } 72 | /// 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | public JT1078HttpContext(HttpListenerContext context, HttpListenerWebSocketContext webSocketContext, IPrincipal user) 79 | { 80 | Context = context; 81 | WebSocketContext = webSocketContext; 82 | User = user; 83 | StartTime = DateTime.Now; 84 | SessionId = Guid.NewGuid().ToString("N"); 85 | FirstSend = false; 86 | } 87 | } 88 | /// 89 | /// 观看视频类型 90 | /// 91 | public enum RTPVideoType 92 | { 93 | /// 94 | /// Http_Flv 95 | /// 96 | Http_Flv, 97 | /// 98 | /// Ws_Flv 99 | /// 100 | Ws_Flv, 101 | /// 102 | /// Http_Hls 103 | /// 104 | Http_Hls, 105 | /// 106 | /// Http_FMp4 107 | /// 108 | Http_FMp4, 109 | /// 110 | /// Ws_FMp4 111 | /// 112 | Ws_FMp4, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Configs/nlog.Unix.config: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Jobs/JT1078SessionClearJob.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Configurations; 3 | using JT1078.Gateway.Services; 4 | using JT1078.Gateway.Sessions; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Diagnostics.CodeAnalysis; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Text.Json; 15 | using System.Threading; 16 | using System.Threading.Tasks; 17 | 18 | 19 | namespace JT1078.Gateway.Jobs 20 | { 21 | /// 22 | /// 清理hls session 23 | /// 24 | public class JT1078SessionClearJob : BackgroundService 25 | { 26 | private readonly ILogger logger; 27 | private readonly JT1078HttpSessionManager HttpSessionManager;//用户链接session 28 | private readonly JT1078SessionManager SessionManager;//设备链接session 29 | private readonly HLSPathStorage hLSPathStorage; 30 | private readonly JT1078Configuration Configuration; 31 | public JT1078SessionClearJob( 32 | ILoggerFactory loggerFactory, 33 | JT1078SessionManager SessionManager, 34 | HLSPathStorage hLSPathStorage, 35 | IOptions jT1078ConfigurationAccessor, 36 | [AllowNull]JT1078HttpSessionManager jT1078HttpSessionManager=null) 37 | { 38 | logger = loggerFactory.CreateLogger(); 39 | HttpSessionManager = jT1078HttpSessionManager; 40 | this.hLSPathStorage = hLSPathStorage; 41 | this.SessionManager = SessionManager; 42 | this.Configuration = jT1078ConfigurationAccessor.Value; 43 | } 44 | 45 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 46 | { 47 | await Task.Run(() => { 48 | while (true) 49 | { 50 | try 51 | { 52 | var hasSessions = HttpSessionManager.GetAll().Where(m => DateTime.Now.Subtract(m.StartTime).TotalSeconds > 60 && m.RTPVideoType == Metadata.RTPVideoType.Http_Hls).ToList();//所有http 的 hls短链接 53 | foreach (var item in hasSessions) 54 | { 55 | var key = $"{item.Sim}_{item.ChannelNo}"; 56 | HttpSessionManager.TryRemove(item.SessionId);//超过120s未访问。 57 | //清楚所有hls文件 58 | string filepath = Path.Combine(Configuration.HlsRootDirectory, key); 59 | if (Directory.Exists(filepath)) 60 | { 61 | Directory.Delete(filepath,true); 62 | } 63 | hLSPathStorage.RemoveAllPath(key);//移除所有缓存 64 | if (logger.IsEnabled(LogLevel.Debug)) 65 | { 66 | logger.LogDebug($"{System.Text.Json.JsonSerializer.Serialize(item)},清除session"); 67 | } 68 | string sim = item.Sim.TrimStart('0'); 69 | var hasTcpSession = HttpSessionManager.GetAllBySimAndChannelNo(sim, item.ChannelNo).Any(m => m.IsWebSocket);//是否存在tcp的 socket链接 70 | var httpFlvSession = HttpSessionManager.GetAllBySimAndChannelNo(sim, item.ChannelNo).Any(m => m.RTPVideoType == Metadata.RTPVideoType.Http_Flv);//是否存在http的 flv长链接 71 | if (!hasTcpSession && !httpFlvSession) 72 | { 73 | //不存在websocket链接和http-flv链接时,主动断开设备链接以节省流量 74 | //移除tcpsession,断开设备链接 75 | if(SessionManager!=null) SessionManager.RemoveByTerminalPhoneNo(sim); 76 | } 77 | } 78 | } 79 | catch(Exception ex) 80 | { 81 | logger.LogError(ex, ex.Message); 82 | } 83 | Thread.Sleep(TimeSpan.FromSeconds(30));//30s 执行一次 84 | } 85 | }, stoppingToken); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Services/JT1078FlvNormalMsgHostedService.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Sessions; 3 | using JT1078.Flv; 4 | using Microsoft.Extensions.Hosting; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Linq; 11 | using Microsoft.Extensions.Logging; 12 | using JT1078.Flv.Extensions; 13 | using Microsoft.Extensions.Caching.Memory; 14 | using JT1078.Protocol; 15 | using System.Text.Json; 16 | using System.Text.Json.Serialization; 17 | 18 | namespace JT1078.Gateway.TestNormalHosting.Services 19 | { 20 | public class JT1078FlvNormalMsgHostedService : BackgroundService 21 | { 22 | private IJT1078MsgConsumer JT1078MsgConsumer; 23 | private JT1078HttpSessionManager HttpSessionManager; 24 | private FlvEncoder FlvEncoder; 25 | private ILogger Logger; 26 | private IMemoryCache memoryCache; 27 | private const string ikey = "IKEY"; 28 | private MessageDispatchDataService messageDispatchDataService; 29 | 30 | public JT1078FlvNormalMsgHostedService( 31 | MessageDispatchDataService messageDispatchDataService, 32 | IMemoryCache memoryCache, 33 | ILoggerFactory loggerFactory, 34 | FlvEncoder flvEncoder, 35 | JT1078HttpSessionManager httpSessionManager, 36 | IJT1078MsgConsumer msgConsumer) 37 | { 38 | Logger = loggerFactory.CreateLogger(); 39 | JT1078MsgConsumer = msgConsumer; 40 | HttpSessionManager = httpSessionManager; 41 | FlvEncoder = flvEncoder; 42 | this.memoryCache = memoryCache; 43 | this.messageDispatchDataService = messageDispatchDataService; 44 | } 45 | protected async override Task ExecuteAsync(CancellationToken stoppingToken) 46 | { 47 | while (!stoppingToken.IsCancellationRequested) 48 | { 49 | var data = await messageDispatchDataService.FlvChannel.Reader.ReadAsync(); 50 | try 51 | { 52 | if (Logger.IsEnabled(LogLevel.Debug)) 53 | { 54 | Logger.LogDebug(JsonSerializer.Serialize(HttpSessionManager.GetAll())); 55 | Logger.LogDebug($"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); 56 | } 57 | 58 | var httpSessions = HttpSessionManager.GetAllBySimAndChannelNo(data.SIM.TrimStart('0'), data.LogicChannelNumber); 59 | var firstHttpSessions = httpSessions.Where(w => !w.FirstSend && (w.RTPVideoType== Metadata.RTPVideoType.Http_Flv || w.RTPVideoType == Metadata.RTPVideoType.Ws_Flv)).ToList(); 60 | var otherHttpSessions = httpSessions.Where(w => w.FirstSend && (w.RTPVideoType == Metadata.RTPVideoType.Http_Flv || w.RTPVideoType == Metadata.RTPVideoType.Ws_Flv)).ToList(); 61 | 62 | if (firstHttpSessions.Count > 0) 63 | { 64 | if (data.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧) 65 | { 66 | var flvVideoBuffer = FlvEncoder.EncoderVideoTag(data, true); 67 | foreach (var session in firstHttpSessions) 68 | { 69 | HttpSessionManager.SendAVData(session, flvVideoBuffer, true); 70 | } 71 | } 72 | } 73 | if (otherHttpSessions.Count > 0) 74 | { 75 | var flvVideoBuffer = FlvEncoder.EncoderVideoTag(data, false); 76 | foreach (var session in otherHttpSessions) 77 | { 78 | HttpSessionManager.SendAVData(session, flvVideoBuffer, false); 79 | } 80 | } 81 | } 82 | catch (Exception ex) 83 | { 84 | Logger.LogError(ex, $"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); 85 | } 86 | } 87 | await Task.CompletedTask; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078GatewayExtensions.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Configurations; 3 | using JT1078.Gateway.Impl; 4 | using JT1078.Gateway.Jobs; 5 | using JT1078.Gateway.Services; 6 | using JT1078.Gateway.Sessions; 7 | using JT1078.Hls.Options; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.DependencyInjection.Extensions; 11 | using Microsoft.Extensions.Options; 12 | using System; 13 | using System.IO; 14 | using System.Net.Http; 15 | using System.Runtime.CompilerServices; 16 | 17 | [assembly: InternalsVisibleTo("JT1078.Gateway.Test")] 18 | 19 | namespace JT1078.Gateway 20 | { 21 | public static class JT1078GatewayExtensions 22 | { 23 | 24 | public static IJT1078GatewayBuilder AddJT1078Gateway(this IServiceCollection serviceDescriptors, IConfiguration configuration) 25 | { 26 | IJT1078Builder builder = new JT1078BuilderDefault(serviceDescriptors); 27 | builder.Services.Configure(configuration.GetSection("JT1078Configuration")); 28 | IJT1078GatewayBuilder jT1078GatewayBuilderDefault = new JT1078GatewayBuilderDefault(builder); 29 | jT1078GatewayBuilderDefault.AddJT1078Core(); 30 | return jT1078GatewayBuilderDefault; 31 | } 32 | 33 | public static IJT1078GatewayBuilder AddJT1078Gateway(this IServiceCollection serviceDescriptors, Action jt1078Options) 34 | { 35 | IJT1078Builder builder = new JT1078BuilderDefault(serviceDescriptors); 36 | builder.Services.Configure(jt1078Options); 37 | IJT1078GatewayBuilder jT1078GatewayBuilderDefault = new JT1078GatewayBuilderDefault(builder); 38 | jT1078GatewayBuilderDefault.AddJT1078Core(); 39 | return jT1078GatewayBuilderDefault; 40 | } 41 | 42 | public static IJT1078GatewayBuilder AddTcp(this IJT1078GatewayBuilder builder) 43 | { 44 | builder.JT1078Builder.Services.AddHostedService(); 45 | builder.JT1078Builder.Services.AddHostedService(); 46 | return builder; 47 | } 48 | 49 | public static IJT1078GatewayBuilder AddUdp(this IJT1078GatewayBuilder builder) 50 | { 51 | builder.JT1078Builder.Services.AddHostedService(); 52 | builder.JT1078Builder.Services.AddHostedService(); 53 | return builder; 54 | } 55 | 56 | public static IJT1078GatewayBuilder AddHttp(this IJT1078GatewayBuilder builder) 57 | { 58 | builder.JT1078Builder.Services.AddSingleton(); 59 | builder.JT1078Builder.Services.AddSingleton(); 60 | builder.JT1078Builder.Services.AddHostedService(); 61 | return builder; 62 | } 63 | 64 | public static IJT1078GatewayBuilder AddHttp(this IJT1078GatewayBuilder builder) 65 | where TIJT1078Authorization: IJT1078Authorization 66 | { 67 | builder.JT1078Builder.Services.AddSingleton(typeof(IJT1078Authorization), typeof(TIJT1078Authorization)); 68 | builder.JT1078Builder.Services.AddSingleton(); 69 | builder.JT1078Builder.Services.AddHostedService(); 70 | return builder; 71 | } 72 | 73 | public static IJT1078GatewayBuilder AddCoordinatorHttpClient(this IJT1078GatewayBuilder builder) 74 | { 75 | builder.JT1078Builder.Services.AddSingleton(); 76 | builder.JT1078Builder.Services.AddHostedService(); 77 | return builder; 78 | } 79 | 80 | internal static IJT1078GatewayBuilder AddJT1078Core(this IJT1078GatewayBuilder builder) 81 | { 82 | builder.JT1078Builder.Services.AddSingleton(); 83 | builder.JT1078Builder.Services.AddSingleton(); 84 | builder.JT1078Builder.Services.AddHostedService(); 85 | 86 | return builder; 87 | } 88 | public static IServiceCollection AddHlsGateway(this IServiceCollection serviceDescriptors, IConfiguration configuration) 89 | { 90 | serviceDescriptors.AddSingleton(); 91 | serviceDescriptors.AddSingleton(); 92 | serviceDescriptors.AddHostedService(); 93 | return serviceDescriptors; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/JT1078.Gateway/Jobs/JT1078HeartbeatJob.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Configurations; 2 | using JT1078.Gateway.Sessions; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Text.Json; 12 | using System.IO; 13 | 14 | namespace JT1078.Gateway.Jobs 15 | { 16 | public class JT1078HeartbeatJob : BackgroundService 17 | { 18 | private readonly ILogger Logger; 19 | 20 | private readonly JT1078SessionManager SessionManager; 21 | 22 | private readonly JT1078HttpSessionManager HttpSessionManager; 23 | 24 | private readonly IOptionsMonitor Configuration; 25 | 26 | private readonly JT1078CoordinatorHttpClient CoordinatorHttpClient; 27 | public JT1078HeartbeatJob( 28 | JT1078CoordinatorHttpClient jT1078CoordinatorHttpClient, 29 | JT1078HttpSessionManager jT1078HttpSessionManager, 30 | IOptionsMonitor jT1078ConfigurationAccessor, 31 | ILoggerFactory loggerFactory, 32 | JT1078SessionManager jT1078SessionManager 33 | ) 34 | { 35 | SessionManager = jT1078SessionManager; 36 | HttpSessionManager = jT1078HttpSessionManager; 37 | Logger = loggerFactory.CreateLogger(); 38 | Configuration = jT1078ConfigurationAccessor; 39 | CoordinatorHttpClient = jT1078CoordinatorHttpClient; 40 | } 41 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 42 | { 43 | try 44 | { 45 | await CoordinatorHttpClient.Reset(); 46 | } 47 | catch (Exception ex) 48 | { 49 | Logger.LogError(ex, $"[Coordinator Reset]"); 50 | } 51 | while (!stoppingToken.IsCancellationRequested) 52 | { 53 | try 54 | { 55 | string json = ""; 56 | using (var stream = new MemoryStream()) 57 | { 58 | using (var writer = new Utf8JsonWriter(stream)) 59 | { 60 | writer.WriteStartObject(); 61 | writer.WriteNumber(nameof(Configuration.CurrentValue.HttpPort), Configuration.CurrentValue.HttpPort); 62 | writer.WriteNumber(nameof(Configuration.CurrentValue.TcpPort), Configuration.CurrentValue.TcpPort); 63 | writer.WriteNumber(nameof(Configuration.CurrentValue.UdpPort), Configuration.CurrentValue.UdpPort); 64 | writer.WriteNumber(nameof(SessionManager.TcpSessionCount), SessionManager.TcpSessionCount); 65 | writer.WriteNumber(nameof(SessionManager.UdpSessionCount), SessionManager.UdpSessionCount); 66 | writer.WriteNumber(nameof(HttpSessionManager.HttpSessionCount), HttpSessionManager.HttpSessionCount); 67 | writer.WriteNumber(nameof(HttpSessionManager.WebSocketSessionCount), HttpSessionManager.WebSocketSessionCount); 68 | writer.WriteStartArray("Sims"); 69 | var sessions = HttpSessionManager.GetAll(); 70 | if (sessions != null) 71 | { 72 | foreach(var session in sessions) 73 | { 74 | writer.WriteStringValue($"{session.Sim}_{session.ChannelNo}_{session.SessionId}"); 75 | } 76 | } 77 | writer.WriteEndArray(); 78 | writer.WriteEndObject(); 79 | } 80 | json = Encoding.UTF8.GetString(stream.ToArray()); 81 | } 82 | if (json != "") 83 | { 84 | if (Logger.IsEnabled(LogLevel.Information)) 85 | { 86 | Logger.LogInformation(json); 87 | } 88 | await CoordinatorHttpClient.Heartbeat(json); 89 | } 90 | } 91 | catch (Exception ex) 92 | { 93 | Logger.LogError(ex, $"[Coordinator Heartbeat]"); 94 | } 95 | finally 96 | { 97 | await Task.Delay(TimeSpan.FromSeconds(Configuration.CurrentValue.CoordinatorHeartbeatTimeSeconds), stoppingToken); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32002.185 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Gateway.Test", "JT1078.Gateway.Tests\JT1078.Gateway.Test\JT1078.Gateway.Test.csproj", "{DAC80A8E-4172-451F-910D-9032BF8640F9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{04C0F72A-5BC4-4CEE-B1E9-CCFAA72E373E}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Gateway.Abstractions", "JT1078.Gateway.Abstractions\JT1078.Gateway.Abstractions.csproj", "{EE50F2A6-5F28-4640-BC20-44A8BED8F311}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Gateway.TestNormalHosting", "JT1078.Gateway.Tests\JT1078.Gateway.TestNormalHosting\JT1078.Gateway.TestNormalHosting.csproj", "{6E2DAA64-E2A1-4459-BE61-E807B4EF2CCE}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Gateway.Coordinator", "JT1078.Gateway.Coordinator\JT1078.Gateway.Coordinator.csproj", "{B0A24D3A-FDA3-4DFE-9C31-032C7C22F303}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Gateway", "JT1078.Gateway\JT1078.Gateway.csproj", "{9042CA92-E01A-46CF-9C82-D954325A69B8}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.Gateway.InMemoryMQ", "JT1078.Gateway.InMemoryMQ\JT1078.Gateway.InMemoryMQ.csproj", "{011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6631FE91-3525-4A84-937B-781C73B343C9}" 21 | ProjectSection(SolutionItems) = preProject 22 | Info.props = Info.props 23 | ..\README.md = ..\README.md 24 | EndProjectSection 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.FMp4", "..\..\JT1078\src\JT1078.FMp4\JT1078.FMp4.csproj", "{579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {DAC80A8E-4172-451F-910D-9032BF8640F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {DAC80A8E-4172-451F-910D-9032BF8640F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {DAC80A8E-4172-451F-910D-9032BF8640F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {DAC80A8E-4172-451F-910D-9032BF8640F9}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {EE50F2A6-5F28-4640-BC20-44A8BED8F311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {EE50F2A6-5F28-4640-BC20-44A8BED8F311}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {EE50F2A6-5F28-4640-BC20-44A8BED8F311}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {EE50F2A6-5F28-4640-BC20-44A8BED8F311}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {6E2DAA64-E2A1-4459-BE61-E807B4EF2CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {6E2DAA64-E2A1-4459-BE61-E807B4EF2CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {6E2DAA64-E2A1-4459-BE61-E807B4EF2CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {6E2DAA64-E2A1-4459-BE61-E807B4EF2CCE}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {B0A24D3A-FDA3-4DFE-9C31-032C7C22F303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {B0A24D3A-FDA3-4DFE-9C31-032C7C22F303}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {B0A24D3A-FDA3-4DFE-9C31-032C7C22F303}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {B0A24D3A-FDA3-4DFE-9C31-032C7C22F303}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {9042CA92-E01A-46CF-9C82-D954325A69B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {9042CA92-E01A-46CF-9C82-D954325A69B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {9042CA92-E01A-46CF-9C82-D954325A69B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {9042CA92-E01A-46CF-9C82-D954325A69B8}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {011934D6-BEE7-4CDA-9F67-4D6D7D672D6C}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {579E0BB3-98C3-4CC8-8771-DFB7F2B86CC4}.Release|Any CPU.Build.0 = Release|Any CPU 62 | EndGlobalSection 63 | GlobalSection(SolutionProperties) = preSolution 64 | HideSolutionNode = FALSE 65 | EndGlobalSection 66 | GlobalSection(NestedProjects) = preSolution 67 | {DAC80A8E-4172-451F-910D-9032BF8640F9} = {04C0F72A-5BC4-4CEE-B1E9-CCFAA72E373E} 68 | {6E2DAA64-E2A1-4459-BE61-E807B4EF2CCE} = {04C0F72A-5BC4-4CEE-B1E9-CCFAA72E373E} 69 | EndGlobalSection 70 | GlobalSection(ExtensibilityGlobals) = postSolution 71 | SolutionGuid = {9172690A-1D5A-491A-ACDD-3AF893980E0B} 72 | EndGlobalSection 73 | EndGlobal 74 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078UdpServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using JT1078.Gateway.Abstractions; 8 | using JT1078.Gateway.Configurations; 9 | using JT1078.Gateway.Sessions; 10 | using JT1078.Protocol; 11 | using JT1078.Protocol.Extensions; 12 | using Microsoft.Extensions.Hosting; 13 | using Microsoft.Extensions.Logging; 14 | using Microsoft.Extensions.Options; 15 | 16 | namespace JT1078.Gateway 17 | { 18 | public class JT1078UdpServer : IHostedService 19 | { 20 | private Socket server; 21 | 22 | private readonly ILogger Logger; 23 | 24 | private readonly JT1078Configuration Configuration; 25 | 26 | private readonly JT1078SessionManager SessionManager; 27 | 28 | private readonly IJT1078MsgProducer jT1078MsgProducer; 29 | 30 | /// 31 | /// 使用队列方式 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | public JT1078UdpServer( 38 | IJT1078MsgProducer jT1078MsgProducer, 39 | IOptions jT1078ConfigurationAccessor, 40 | ILoggerFactory loggerFactory, 41 | JT1078SessionManager jT1078SessionManager) 42 | { 43 | SessionManager = jT1078SessionManager; 44 | Logger = loggerFactory.CreateLogger(); 45 | Configuration = jT1078ConfigurationAccessor.Value; 46 | this.jT1078MsgProducer = jT1078MsgProducer; 47 | InitServer(); 48 | } 49 | 50 | private void InitServer() 51 | { 52 | var IPEndPoint = new System.Net.IPEndPoint(IPAddress.Any, Configuration.UdpPort); 53 | server = new Socket(IPEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); 54 | server.Bind(IPEndPoint); 55 | } 56 | public Task StartAsync(CancellationToken cancellationToken) 57 | { 58 | Logger.LogInformation($"JT1078 Udp Server start at {IPAddress.Any}:{Configuration.UdpPort}."); 59 | Task.Factory.StartNew(async () => 60 | { 61 | while (!cancellationToken.IsCancellationRequested) 62 | { 63 | var buffer = ArrayPool.Shared.Rent(Configuration.MiniNumBufferSize); 64 | try 65 | { 66 | var segment = new ArraySegment(buffer); 67 | var result = await server.ReceiveMessageFromAsync(segment, SocketFlags.None, server.LocalEndPoint); 68 | ReaderBuffer(buffer.AsSpan(0, result.ReceivedBytes), server, result); 69 | } 70 | catch (System.ObjectDisposedException ex) 71 | { 72 | 73 | } 74 | catch (AggregateException ex) 75 | { 76 | Logger.LogError(ex, "Receive MessageFrom Async"); 77 | } 78 | #pragma warning disable CA1031 // Do not catch general exception types 79 | catch (Exception ex) 80 | { 81 | Logger.LogError(ex, $"Received Bytes"); 82 | } 83 | #pragma warning restore CA1031 // Do not catch general exception types 84 | finally 85 | { 86 | ArrayPool.Shared.Return(buffer); 87 | } 88 | } 89 | }, cancellationToken); 90 | return Task.CompletedTask; 91 | } 92 | private void ReaderBuffer(ReadOnlySpan buffer, Socket socket, SocketReceiveMessageFromResult receiveMessageFromResult) 93 | { 94 | try 95 | { 96 | var package = JT1078Serializer.Deserialize(buffer); 97 | package.SIM = package.SIM.TrimStart('0'); 98 | if (Logger.IsEnabled(LogLevel.Trace)) Logger.LogTrace($"[Accept Hex {receiveMessageFromResult.RemoteEndPoint}]:{buffer.ToArray().ToHexString()}"); 99 | var session = SessionManager.TryLink(package.SIM, socket, receiveMessageFromResult.RemoteEndPoint); 100 | if (Logger.IsEnabled(LogLevel.Information)) 101 | { 102 | Logger.LogInformation($"[Connected]:{receiveMessageFromResult.RemoteEndPoint}"); 103 | } 104 | jT1078MsgProducer.ProduceAsync(package.SIM, buffer.ToArray()); 105 | } 106 | catch (NotImplementedException ex) 107 | { 108 | Logger.LogError(ex.Message); 109 | } 110 | #pragma warning disable CA1031 // Do not catch general exception types 111 | catch (Exception ex) 112 | { 113 | Logger.LogError(ex, $"[ReaderBuffer]:{ buffer.ToArray().ToHexString()}"); 114 | } 115 | #pragma warning restore CA1031 // Do not catch general exception types 116 | } 117 | public Task StopAsync(CancellationToken cancellationToken) 118 | { 119 | Logger.LogInformation("JT1078 Udp Server Stop"); 120 | try 121 | { 122 | if (server?.Connected ?? false) 123 | server.Shutdown(SocketShutdown.Both); 124 | server?.Close(); 125 | } 126 | catch (System.ObjectDisposedException ex) 127 | { 128 | 129 | } 130 | return Task.CompletedTask; 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/fmp4_demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSocket MSE Fmp4 demo 7 | 8 | 9 |

MSE FMp4 Demo

10 | 13 | 121 | 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | 56 | # StyleCop 57 | StyleCopReport.xml 58 | 59 | # Files built by Visual Studio 60 | *_i.c 61 | *_p.c 62 | *_i.h 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.iobj 67 | *.pch 68 | *.pdb 69 | *.ipdb 70 | *.pgc 71 | *.pgd 72 | *.rsp 73 | *.sbr 74 | *.tlb 75 | *.tli 76 | *.tlh 77 | *.tmp 78 | *.tmp_proj 79 | *.log 80 | *.vspscc 81 | *.vssscc 82 | .builds 83 | *.pidb 84 | *.svclog 85 | *.scc 86 | 87 | # Chutzpah Test files 88 | _Chutzpah* 89 | 90 | # Visual C++ cache files 91 | ipch/ 92 | *.aps 93 | *.ncb 94 | *.opendb 95 | *.opensdf 96 | *.sdf 97 | *.cachefile 98 | *.VC.db 99 | *.VC.VC.opendb 100 | 101 | # Visual Studio profiler 102 | *.psess 103 | *.vsp 104 | *.vspx 105 | *.sap 106 | 107 | # Visual Studio Trace Files 108 | *.e2e 109 | 110 | # TFS 2012 Local Workspace 111 | $tf/ 112 | 113 | # Guidance Automation Toolkit 114 | *.gpState 115 | 116 | # ReSharper is a .NET coding add-in 117 | _ReSharper*/ 118 | *.[Rr]e[Ss]harper 119 | *.DotSettings.user 120 | 121 | # JustCode is a .NET coding add-in 122 | .JustCode 123 | 124 | # TeamCity is a build add-in 125 | _TeamCity* 126 | 127 | # DotCover is a Code Coverage Tool 128 | *.dotCover 129 | 130 | # AxoCover is a Code Coverage Tool 131 | .axoCover/* 132 | !.axoCover/settings.json 133 | 134 | # Visual Studio code coverage results 135 | *.coverage 136 | *.coveragexml 137 | 138 | # NCrunch 139 | _NCrunch_* 140 | .*crunch*.local.xml 141 | nCrunchTemp_* 142 | 143 | # MightyMoose 144 | *.mm.* 145 | AutoTest.Net/ 146 | 147 | # Web workbench (sass) 148 | .sass-cache/ 149 | 150 | # Installshield output folder 151 | [Ee]xpress/ 152 | 153 | # DocProject is a documentation generator add-in 154 | DocProject/buildhelp/ 155 | DocProject/Help/*.HxT 156 | DocProject/Help/*.HxC 157 | DocProject/Help/*.hhc 158 | DocProject/Help/*.hhk 159 | DocProject/Help/*.hhp 160 | DocProject/Help/Html2 161 | DocProject/Help/html 162 | 163 | # Click-Once directory 164 | publish/ 165 | 166 | # Publish Web Output 167 | *.[Pp]ublish.xml 168 | *.azurePubxml 169 | # Note: Comment the next line if you want to checkin your web deploy settings, 170 | # but database connection strings (with potential passwords) will be unencrypted 171 | *.pubxml 172 | *.publishproj 173 | 174 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 175 | # checkin your Azure Web App publish settings, but sensitive information contained 176 | # in these scripts will be unencrypted 177 | PublishScripts/ 178 | 179 | # NuGet Packages 180 | *.nupkg 181 | # The packages folder can be ignored because of Package Restore 182 | **/[Pp]ackages/* 183 | # except build/, which is used as an MSBuild target. 184 | !**/[Pp]ackages/build/ 185 | # Uncomment if necessary however generally it will be regenerated when needed 186 | #!**/[Pp]ackages/repositories.config 187 | # NuGet v3's project.json files produces more ignorable files 188 | *.nuget.props 189 | *.nuget.targets 190 | 191 | # Microsoft Azure Build Output 192 | csx/ 193 | *.build.csdef 194 | 195 | # Microsoft Azure Emulator 196 | ecf/ 197 | rcf/ 198 | 199 | # Windows Store app package directories and files 200 | AppPackages/ 201 | BundleArtifacts/ 202 | Package.StoreAssociation.xml 203 | _pkginfo.txt 204 | *.appx 205 | 206 | # Visual Studio cache files 207 | # files ending in .cache can be ignored 208 | *.[Cc]ache 209 | # but keep track of directories ending in .cache 210 | !*.[Cc]ache/ 211 | 212 | # Others 213 | ClientBin/ 214 | ~$* 215 | *~ 216 | *.dbmdl 217 | *.dbproj.schemaview 218 | *.jfm 219 | *.pfx 220 | *.publishsettings 221 | orleans.codegen.cs 222 | 223 | # Including strong name files can present a security risk 224 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 225 | #*.snk 226 | 227 | # Since there are multiple workflows, uncomment next line to ignore bower_components 228 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 229 | #bower_components/ 230 | 231 | # RIA/Silverlight projects 232 | Generated_Code/ 233 | 234 | # Backup & report files from converting an old project file 235 | # to a newer Visual Studio version. Backup files are not needed, 236 | # because we have git ;-) 237 | _UpgradeReport_Files/ 238 | Backup*/ 239 | UpgradeLog*.XML 240 | UpgradeLog*.htm 241 | ServiceFabricBackup/ 242 | *.rptproj.bak 243 | 244 | # SQL Server files 245 | *.mdf 246 | *.ldf 247 | *.ndf 248 | 249 | # Business Intelligence projects 250 | *.rdl.data 251 | *.bim.layout 252 | *.bim_*.settings 253 | *.rptproj.rsuser 254 | 255 | # Microsoft Fakes 256 | FakesAssemblies/ 257 | 258 | # GhostDoc plugin setting file 259 | *.GhostDoc.xml 260 | 261 | # Node.js Tools for Visual Studio 262 | .ntvs_analysis.dat 263 | node_modules/ 264 | 265 | # Visual Studio 6 build log 266 | *.plg 267 | 268 | # Visual Studio 6 workspace options file 269 | *.opt 270 | 271 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 272 | *.vbw 273 | 274 | # Visual Studio LightSwitch build output 275 | **/*.HTMLClient/GeneratedArtifacts 276 | **/*.DesktopClient/GeneratedArtifacts 277 | **/*.DesktopClient/ModelManifest.xml 278 | **/*.Server/GeneratedArtifacts 279 | **/*.Server/ModelManifest.xml 280 | _Pvt_Extensions 281 | 282 | # Paket dependency manager 283 | .paket/paket.exe 284 | paket-files/ 285 | 286 | # FAKE - F# Make 287 | .fake/ 288 | 289 | # JetBrains Rider 290 | .idea/ 291 | *.sln.iml 292 | 293 | # CodeRush 294 | .cr/ 295 | 296 | # Python Tools for Visual Studio (PTVS) 297 | __pycache__/ 298 | *.pyc 299 | 300 | # Cake - Uncomment if you are using it 301 | # tools/** 302 | # !tools/packages.config 303 | 304 | # Tabs Studio 305 | *.tss 306 | 307 | # Telerik's JustMock configuration file 308 | *.jmconfig 309 | 310 | # BizTalk build output 311 | *.btp.cs 312 | *.btm.cs 313 | *.odx.cs 314 | *.xsd.cs 315 | 316 | # OpenCover UI analysis results 317 | OpenCover/ 318 | 319 | # Azure Stream Analytics local run output 320 | ASALocalRun/ 321 | 322 | # MSBuild Binary and Structured Log 323 | *.binlog 324 | 325 | # NVidia Nsight GPU debugger configuration file 326 | *.nvuser 327 | 328 | # MFractors (Xamarin productivity tool) working folder 329 | .mfractor/ 330 | /nupkgs 331 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Program.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Flv; 2 | using JT1078.FMp4; 3 | using JT1078.Gateway.InMemoryMQ; 4 | using JT1078.Gateway.TestNormalHosting.Services; 5 | using JT1078.Hls; 6 | using JT1078.Hls.Options; 7 | using JT1078.Protocol.H264; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | using NLog.Extensions.Logging; 14 | using System; 15 | using System.IO; 16 | using System.Threading.Tasks; 17 | 18 | namespace JT1078.Gateway.TestNormalHosting 19 | { 20 | class Program 21 | { 22 | static async Task Main(string[] args) 23 | { 24 | var serverHostBuilder = new HostBuilder() 25 | .ConfigureAppConfiguration((hostingContext, config) => 26 | { 27 | config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); 28 | config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); 29 | }) 30 | .ConfigureLogging((context, logging) => 31 | { 32 | //logging.SetMinimumLevel(LogLevel.Trace); 33 | Console.WriteLine($"Environment.OSVersion.Platform:{Environment.OSVersion.Platform.ToString()}"); 34 | NLog.LogManager.LoadConfiguration($"Configs/nlog.{Environment.OSVersion.Platform.ToString()}.config"); 35 | logging.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true }); 36 | }) 37 | .ConfigureServices((hostContext, services) => 38 | { 39 | services.AddMemoryCache(); 40 | //flv视频解码器 41 | services.AddSingleton(); 42 | //hls视频解码器 43 | services.AddSingleton(); 44 | //h264 45 | services.AddSingleton(); 46 | services.AddSingleton(); 47 | //添加hls依赖项 48 | services.AddHlsGateway(hostContext.Configuration); 49 | services.Configure(hostContext.Configuration.GetSection("M3U8Option")); 50 | var m3u8Option = services.BuildServiceProvider().GetRequiredService>().Value; 51 | services.AddSingleton(m3u8Option); 52 | 53 | services.AddSingleton(); 54 | 55 | //使用内存队列实现会话通知 56 | services.AddJT1078Gateway(hostContext.Configuration) 57 | .AddTcp() 58 | .AddUdp() 59 | .AddHttp() 60 | //.AddCoordinatorHttpClient() 61 | .AddMsgProducer() 62 | .AddMsgConsumer(); 63 | //内存队列没有做分发,可以自己实现。 64 | services.AddHostedService(); 65 | services.AddHostedService(); 66 | services.AddHostedService(); 67 | 68 | services.AddSingleton(); 69 | services.AddHostedService(); 70 | 71 | }); 72 | //测试1: 73 | //发送完整包 74 | //303163648162000000190130503701010000016f95973f840000000003b600000001674d001495a85825900000000168ee3c800000000106e5010d800000000165b80000090350bfdfe840b5b35c676079446e3ffe2f5240e25cc6b35cebac31720656a853ba1b571246fb858eaa5d8266acb95b92705fd187f1fd20ff0ca6b62c4cbcb3b662f5d61c016928ca82b411acdc4df6edb2034624b992eee9b6c241e1903bf9477c6e4293b65ba75e98d5a2566da6f71c85e1052a9d5ed35c393b1a73b181598749f3d26f6fbf48f0be61c673fcb9f2b0d305794bed03af5e3cedff7768bed3120261d6f3547a6d519943c2afcb80e423c9e6db088a06200dbfaa81edc5bc0de67957e791f67bf040ef944f7d62983d32517b2fb2d9572a71340c225617231bc0d98e66d19fe81a19b44280860b273f700bf3f3444a928e93fefc716e2af46995fbb658d0580a49e42f6835270c8c154abe28a17f76550b1b1fafe62945f80490b3f780fe9bb4d4b4107eac3d50b8c99d1a191f6754992096683fb0f599846bae759b06222079f5404be39e4416136c7c42255b0e7ca42d86fc2227892406d61f9816bc125d017989a671f13f2f4052e018b1fb02460802029a049a23d2ffeea6ac552109d35aa8731483fb2cae963987156056cafb32436a23a0dc918fb2440b14c9e6124441e7bb3b08706066d1ddab512267767b6e522f80732e67046ff5ad4d8193bf5cc5c05ccceb73a36b6c3ea39fa91bb308c8bb7bf88515d9c52409128e8b94e33e48a5396c35c20bd83b7c0e6d3d4a24bc14e84810066c686c6c04e687c41123fe87c89d5fa07b0095e7f82d3b07e72570163c47444bdde16ae9bfacd540df047e8ee34e98ff33178da5c7e6be9272e6dcfbb6db7e678a6d1d3832226c9bf85afa14feac15a270d5d3724a121b8fc9b40f0d37bb7f432de5421d286a65313a6efd251f7ed75b4ef6557975af5da5df2b87a0bbc1cb58183c4c1e24fdc4eb016777af1a6fa4a29d3eed7c4463482e591a6dc20540cabb6d7dd29cbb8ffdacafdaac2dd36db70fefe14fdeec85ef5fe01bb104d2d6439dbd7ceefc87007ce07b8409751dd7c21aa9a537f5fdefdef7d6ceba8d5ae876522f75dedd472e4dde1284e71380ee75ed313b2b9b9a94a56ebd03ae36b64a3b35abbdc7ba380016218201d156658ed9b5632f80f921879063e9037cd3509d01a2e91c17e03d892e2bc381ac723eba266497a1fbb0dc77ab3f4a9a981f95977b025b005a0e09b1add481888333927963fc5e5bf376655cb00e4ca8841fa450c8653f91cf2f3fb0247dbcace5dfde3af4a854f9fa2aaaa33706a78321332273ab4ee837ff4f8eba08676e7f889464a842b8e3e4a579d2 75 | //测试2: 76 | //发送头部包 77 | //303163648162000000190130503701010000016f95973f840000000003b6 78 | //发送数据体 79 | //00000001674d001495a85825900000000168ee3c800000000106e5010d800000000165b80000090350bfdfe840b5b35c676079446e3ffe2f5240e25cc6b35cebac31720656a853ba1b571246fb858eaa5d8266acb95b92705fd187f1fd20ff0ca6b62c4cbcb3b662f5d61c016928ca82b411acdc4df6edb2034624b992eee9b6c241e1903bf9477c6e4293b65ba75e98d5a2566da6f71c85e1052a9d5ed35c393b1a73b181598749f3d26f6fbf48f0be61c673fcb9f2b0d305794bed03af5e3cedff7768bed3120261d6f3547a6d519943c2afcb80e423c9e6db088a06200dbfaa81edc5bc0de67957e791f67bf040ef944f7d62983d32517b2fb2d9572a71340c225617231bc0d98e66d19fe81a19b44280860b273f700bf3f3444a928e93fefc716e2af46995fbb658d0580a49e42f6835270c8c154abe28a17f76550b1b1fafe62945f80490b3f780fe9bb4d4b4107eac3d50b8c99d1a191f6754992096683fb0f599846bae759b06222079f5404be39e4416136c7c42255b0e7ca42d86fc2227892406d61f9816bc125d017989a671f13f2f4052e018b1fb02460802029a049a23d2ffeea6ac552109d35aa8731483fb2cae963987156056cafb32436a23a0dc918fb2440b14c9e6124441e7bb3b08706066d1ddab512267767b6e522f80732e67046ff5ad4d8193bf5cc5c05ccceb73a36b6c3ea39fa91bb308c8bb7bf88515d9c52409128e8b94e33e48a5396c35c20bd83b7c0e6d3d4a24bc14e84810066c686c6c04e687c41123fe87c89d5fa07b0095e7f82d3b07e72570163c47444bdde16ae9bfacd540df047e8ee34e98ff33178da5c7e6be9272e6dcfbb6db7e678a6d1d3832226c9bf85afa14feac15a270d5d3724a121b8fc9b40f0d37bb7f432de5421d286a65313a6efd251f7ed75b4ef6557975af5da5df2b87a0bbc1cb58183c4c1e24fdc4eb016777af1a6fa4a29d3eed7c4463482e591a6dc20540cabb6d7dd29cbb8ffdacafdaac2dd36db70fefe14fdeec85ef5fe01bb104d2d6439dbd7ceefc87007ce07b8409751dd7c21aa9a537f5fdefdef7d6ceba8d5ae876522f75dedd472e4dde1284e71380ee75ed313b2b9b9a94a56ebd03ae36b64a3b35abbdc7ba380016218201d156658ed9b5632f80f921879063e9037cd3509d01a2e91c17e03d892e2bc381ac723eba266497a1fbb0dc77ab3f4a9a981f95977b025b005a0e09b1add481888333927963fc5e5bf376655cb00e4ca8841fa450c8653f91cf2f3fb0247dbcace5dfde3af4a854f9fa2aaaa33706a78321332273ab4ee837ff4f8eba08676e7f889464a842b8e3e4a579d2 80 | await serverHostBuilder.RunConsoleAsync(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Services/HLSRequestManager.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Configurations; 2 | using JT1078.Gateway.Extensions; 3 | using JT1078.Gateway.Metadata; 4 | using JT1078.Gateway.Sessions; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | using System; 10 | using System.Collections.Concurrent; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Net; 15 | using System.Security.Principal; 16 | using System.Text; 17 | using System.Threading; 18 | using System.Threading.Tasks; 19 | 20 | namespace JT1078.Gateway.Services 21 | { 22 | /// 23 | /// Hls请求管理 24 | /// 25 | public class HLSRequestManager 26 | { 27 | private readonly JT1078Configuration Configuration; 28 | private readonly JT1078HttpSessionManager HttpSessionManager; 29 | private readonly HLSPathStorage hLSPathStorage; 30 | private readonly ILogger Logger; 31 | 32 | public HLSRequestManager(IOptions jT1078ConfigurationAccessor, 33 | JT1078HttpSessionManager httpSessionManager, 34 | HLSPathStorage hLSPathStorage, 35 | ILoggerFactory loggerFactory) 36 | { 37 | HttpSessionManager = httpSessionManager; 38 | this.hLSPathStorage = hLSPathStorage; 39 | Configuration = jT1078ConfigurationAccessor.Value; 40 | Logger = loggerFactory.CreateLogger(); 41 | } 42 | /// 43 | /// 处理hls实时视频请求 44 | /// 45 | /// 46 | /// 47 | /// 48 | public async void HandleHlsRequest(HttpListenerContext context, IPrincipal principal, JT1078AVInfo jT1078AVInfo) 49 | { 50 | string filename = Path.GetFileName(context.Request.Url.AbsolutePath.ToString()); 51 | string filepath = Path.Combine(Configuration.HlsRootDirectory, jT1078AVInfo.ToString(), filename); 52 | if (hLSPathStorage.ExsitPath(filepath)) 53 | { 54 | try 55 | { 56 | using (FileStream sr = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 57 | { 58 | if (filename.Contains("m3u8")) 59 | { 60 | await context.HttpM3U8Async(sr); 61 | } 62 | else if (filename.Contains("ts")) 63 | { 64 | await context.HttpTsAsync(sr); 65 | } 66 | else 67 | { 68 | context.Http404(); 69 | } 70 | } 71 | } 72 | catch (Exception ex) 73 | { 74 | Logger.LogError(ex, ex.Message); 75 | context.Http404(); 76 | } 77 | } 78 | else 79 | { 80 | if (!File.Exists(filepath)) 81 | { 82 | if (filename.ToLower().Contains("m3u8")) 83 | { 84 | var directory = Path.Combine(Configuration.HlsRootDirectory, jT1078AVInfo.ToString()); 85 | if (!Directory.Exists(directory)) 86 | { 87 | Directory.CreateDirectory(directory); 88 | } 89 | if (!hLSPathStorage.ExistFileSystemWatcher(directory)) { 90 | var fileSystemWatcher = new FileSystemWatcher(); 91 | fileSystemWatcher.Path = directory; 92 | fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite; //NotifyFilters.CreateTime 93 | fileSystemWatcher.Filter = "*.m3u8"; // Only watch text files. 94 | fileSystemWatcher.Changed += async (sender, arg) => 95 | { 96 | if (context.Response.ContentLength64 != 0) return; 97 | //wwwroot\1234_2\live.m3u8 98 | //var key = arg.FullPath.Replace(arg.Name, "").Substring(arg.FullPath.Replace(arg.Name, "").IndexOf("\\")).Replace("\\", ""); 99 | var key = arg.FullPath.Substring(arg.FullPath.IndexOf("\\") + 1, (arg.FullPath.LastIndexOf("\\") - arg.FullPath.IndexOf("\\")) - 1); 100 | var sim = key.Split("_")[0]; 101 | var channel = int.Parse(key.Split("_")[1]); 102 | try 103 | { 104 | using (FileStream sr = new FileStream(arg.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 105 | { 106 | hLSPathStorage.AddPath(arg.FullPath, key); 107 | await context.HttpM3U8Async(sr); 108 | } 109 | } 110 | catch (Exception ex) 111 | { 112 | Logger.LogError(ex, $"{context.Request.Url}"); 113 | context.Http404(); 114 | } 115 | finally 116 | { 117 | hLSPathStorage.DeleteFileSystemWatcher(directory); 118 | } 119 | }; 120 | fileSystemWatcher.EnableRaisingEvents = true; // Begin watching. 121 | hLSPathStorage.AddFileSystemWatcher(directory, fileSystemWatcher); 122 | } 123 | } 124 | else 125 | { 126 | context.Http404(); 127 | return; 128 | } 129 | } 130 | else 131 | { 132 | hLSPathStorage.AddPath(filepath, jT1078AVInfo.ToString()); 133 | using (FileStream sr = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 134 | { 135 | if (filename.Contains("m3u8")) 136 | { 137 | await context.HttpM3U8Async(sr); 138 | } 139 | else if (filename.Contains("ts")) 140 | { 141 | await context.HttpTsAsync(sr); 142 | } 143 | else 144 | { 145 | context.Http404(); 146 | } 147 | } 148 | } 149 | var jT1078HttpContext = new JT1078HttpContext(context, principal); 150 | jT1078HttpContext.Sim = jT1078AVInfo.Sim; 151 | jT1078HttpContext.ChannelNo = jT1078AVInfo.ChannelNo; 152 | jT1078HttpContext.RTPVideoType = RTPVideoType.Http_Hls; 153 | HttpSessionManager.AddOrUpdateHlsSession(jT1078HttpContext); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Services/JT1078FMp4NormalMsgHostedService.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Sessions; 2 | using Microsoft.Extensions.Hosting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Linq; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Caching.Memory; 10 | using JT1078.Protocol; 11 | using System.Text.Json; 12 | using JT1078.Protocol.H264; 13 | using System.Collections.Concurrent; 14 | using JT1078.FMp4; 15 | using JT1078.Protocol.Extensions; 16 | using System.IO; 17 | 18 | namespace JT1078.Gateway.TestNormalHosting.Services 19 | { 20 | public class JT1078FMp4NormalMsgHostedService : IHostedService 21 | { 22 | private JT1078HttpSessionManager HttpSessionManager; 23 | private FMp4Encoder FM4Encoder; 24 | private ILogger Logger; 25 | private const string ikey = "IFMp4KEY"; 26 | private MessageDispatchDataService messageDispatchDataService; 27 | private ConcurrentDictionary avFrameDict; 28 | private H264Decoder H264Decoder; 29 | BlockingCollection<(string SIM, byte ChannelNo,byte[]FirstBuffer, byte[] AVFrameInfoBuffer, byte[] OtherBuffer)> FMp4Blocking; 30 | public JT1078FMp4NormalMsgHostedService( 31 | MessageDispatchDataService messageDispatchDataService, 32 | ILoggerFactory loggerFactory, 33 | FMp4Encoder fM4Encoder, 34 | H264Decoder h264Decoder, 35 | JT1078HttpSessionManager httpSessionManager) 36 | { 37 | Logger = loggerFactory.CreateLogger(); 38 | HttpSessionManager = httpSessionManager; 39 | FM4Encoder = fM4Encoder; 40 | H264Decoder= h264Decoder; 41 | this.messageDispatchDataService = messageDispatchDataService; 42 | avFrameDict = new ConcurrentDictionary(); 43 | FMp4Blocking=new BlockingCollection<(string SIM, byte ChannelNo, byte[] FirstBuffer, byte[] AVFrameInfoBuffer, byte[] OtherBuffer)>(); 44 | } 45 | 46 | public Task StartAsync(CancellationToken cancellationToken) 47 | { 48 | Task.Run(async () => { 49 | while (!cancellationToken.IsCancellationRequested) 50 | { 51 | var data = await messageDispatchDataService.FMp4Channel.Reader.ReadAsync(); 52 | try 53 | { 54 | //if (Logger.IsEnabled(LogLevel.Debug)) 55 | //{ 56 | // Logger.LogDebug(JsonSerializer.Serialize(HttpSessionManager.GetAll())); 57 | // Logger.LogDebug($"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); 58 | //} 59 | bool keyframe = data.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧; 60 | JT1078AVFrame avframe = H264Decoder.ParseAVFrame(data); 61 | if (avframe.Nalus!= null && avframe.Nalus.Count>0) 62 | { 63 | if(!avFrameDict.TryGetValue(data.GetAVKey(),out FMp4AVContext cacheFrame)) 64 | { 65 | cacheFrame=new FMp4AVContext(); 66 | avFrameDict.TryAdd(data.GetAVKey(), cacheFrame); 67 | } 68 | if(keyframe) 69 | { 70 | if(avframe.SPS!=null && avframe.PPS != null) 71 | { 72 | cacheFrame.AVFrameInfoBuffer = JsonSerializer.SerializeToUtf8Bytes( 73 | new { Codecs = avframe .ToCodecs(), Width = avframe .Width, Height =avframe.Height}); 74 | cacheFrame.FirstCacheBuffer = FM4Encoder.FirstVideoBox(avframe); 75 | } 76 | } 77 | if (cacheFrame.FirstCacheBuffer != null) 78 | { 79 | FMp4Blocking.Add((data.SIM, data.LogicChannelNumber, cacheFrame.FirstCacheBuffer, cacheFrame.AVFrameInfoBuffer,FM4Encoder.OtherVideoBox(avframe.Nalus, data.GetAVKey(), keyframe))); 80 | } 81 | } 82 | } 83 | catch (Exception ex) 84 | { 85 | Logger.LogError(ex, $"{data.SIM},{data.SN},{data.LogicChannelNumber},{data.Label3.DataType.ToString()},{data.Label3.SubpackageType.ToString()},{data.Bodies.ToHexString()}"); 86 | } 87 | } 88 | }); 89 | Task.Run(() => { 90 | //var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "JT1078_7_4_4.mp4"); 91 | //if (File.Exists(filepath)) 92 | //{ 93 | // File.Delete(filepath); 94 | //} 95 | //using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); 96 | try 97 | { 98 | foreach (var item in FMp4Blocking.GetConsumingEnumerable(cancellationToken)) 99 | { 100 | var httpSessions = HttpSessionManager.GetAllBySimAndChannelNo(item.SIM.TrimStart('0'), item.ChannelNo); 101 | var firstHttpSessions = httpSessions.Where(w => !w.FirstSend && (w.RTPVideoType == Metadata.RTPVideoType.Http_FMp4 || w.RTPVideoType == Metadata.RTPVideoType.Ws_FMp4)).ToList(); 102 | var otherHttpSessions = httpSessions.Where(w => w.FirstSend && (w.RTPVideoType == Metadata.RTPVideoType.Http_FMp4 || w.RTPVideoType == Metadata.RTPVideoType.Ws_FMp4)).ToList(); 103 | if (firstHttpSessions.Count > 0) 104 | { 105 | //首帧 106 | foreach (var session in firstHttpSessions) 107 | { 108 | HttpSessionManager.SendAVData(session, item.AVFrameInfoBuffer, true); 109 | HttpSessionManager.SendAVData(session, item.FirstBuffer, false); 110 | //fileStream.Write(item.FirstBuffer); 111 | HttpSessionManager.SendAVData(session, item.OtherBuffer, false); 112 | //fileStream.Write(item.OtherBuffer); 113 | } 114 | } 115 | if (otherHttpSessions.Count > 0) 116 | { 117 | //非首帧 118 | foreach (var session in otherHttpSessions) 119 | { 120 | HttpSessionManager.SendAVData(session, item.OtherBuffer, false); 121 | //fileStream.Write(item.OtherBuffer); 122 | } 123 | } 124 | } 125 | } 126 | catch (Exception ex) 127 | { 128 | 129 | } 130 | //fileStream.Close(); 131 | }); 132 | return Task.CompletedTask; 133 | } 134 | 135 | public Task StopAsync(CancellationToken cancellationToken) 136 | { 137 | return Task.CompletedTask; 138 | } 139 | 140 | public class FMp4AVContext 141 | { 142 | public byte[] AVFrameInfoBuffer { get; set; } 143 | public byte[] FirstCacheBuffer { get; set; } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Sessions/JT1078HttpSessionManager.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Extensions; 2 | using JT1078.Gateway.Metadata; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Net.WebSockets; 10 | using System.Text; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | namespace JT1078.Gateway.Sessions 15 | { 16 | public class JT1078HttpSessionManager 17 | { 18 | public ConcurrentDictionary Sessions { get; } 19 | private ILogger Logger; 20 | public JT1078HttpSessionManager(ILoggerFactory loggerFactory) 21 | { 22 | Sessions = new ConcurrentDictionary(); 23 | Logger = loggerFactory.CreateLogger(); 24 | } 25 | 26 | public bool TryAdd(JT1078HttpContext httpContext) 27 | { 28 | 29 | return Sessions.TryAdd(httpContext.SessionId, httpContext); 30 | } 31 | 32 | public void AddOrUpdateHlsSession(JT1078HttpContext httpContext) 33 | { 34 | //如果不存在就添加,如果存在则删除后添加(保持key和value中的sessionid一致) 35 | var session = Sessions.FirstOrDefault(m => m.Value.Sim == httpContext.Sim && m.Value.ChannelNo == httpContext.ChannelNo && m.Value.RTPVideoType == RTPVideoType.Http_Hls); 36 | if (!string.IsNullOrEmpty(session.Key)) 37 | { 38 | Sessions.TryRemove(session.Key, out var _); 39 | } 40 | Sessions.TryAdd(httpContext.SessionId, httpContext); 41 | } 42 | 43 | public async void TryRemove(string sessionId) 44 | { 45 | if(Sessions.TryRemove(sessionId, out JT1078HttpContext session)) 46 | { 47 | try 48 | { 49 | if (session.IsWebSocket) 50 | { 51 | await session.WebSocketClose("close"); 52 | } 53 | else 54 | { 55 | 56 | await session.HttpClose(); 57 | } 58 | } 59 | catch (Exception) 60 | { 61 | 62 | } 63 | } 64 | } 65 | 66 | public async void TryRemoveBySim(string sim) 67 | { 68 | var keys=Sessions.Where(f => f.Value.Sim == sim).Select(s => s.Key); 69 | foreach(var key in keys) 70 | { 71 | if (Sessions.TryRemove(key, out JT1078HttpContext session)) 72 | { 73 | try 74 | { 75 | if (session.IsWebSocket) 76 | { 77 | await session.WebSocketClose("close"); 78 | } 79 | else 80 | { 81 | 82 | await session.HttpClose(); 83 | } 84 | } 85 | catch (Exception) 86 | { 87 | 88 | } 89 | } 90 | } 91 | } 92 | 93 | private void remove(string sessionId) 94 | { 95 | if (Sessions.TryRemove(sessionId, out JT1078HttpContext session)) 96 | { 97 | } 98 | } 99 | 100 | /// 101 | /// 发送音视频数据 102 | /// 103 | /// 104 | /// 105 | /// 106 | public async void SendAVData(JT1078HttpContext httpContext, byte[] data, bool firstSend) 107 | { 108 | if (httpContext.IsWebSocket) 109 | { 110 | if (firstSend) 111 | { 112 | httpContext.FirstSend = firstSend; 113 | Sessions.TryUpdate(httpContext.SessionId, httpContext, httpContext); 114 | } 115 | try 116 | { 117 | await httpContext.WebSocketSendBinaryAsync(data); 118 | } 119 | catch (Exception ex) 120 | { 121 | if (Logger.IsEnabled(LogLevel.Information)) 122 | { 123 | Logger.LogInformation($"[ws close]:{httpContext.SessionId}-{httpContext.Sim}-{httpContext.ChannelNo}-{httpContext.StartTime:yyyyMMddhhmmss}"); 124 | } 125 | remove(httpContext.SessionId); 126 | } 127 | } 128 | else 129 | { 130 | if (firstSend) 131 | { 132 | httpContext.FirstSend = firstSend; 133 | Sessions.TryUpdate(httpContext.SessionId, httpContext, httpContext); 134 | try 135 | { 136 | await httpContext.HttpSendFirstChunked(data); 137 | } 138 | catch (Exception ex) 139 | { 140 | if (Logger.IsEnabled(LogLevel.Information)) 141 | { 142 | Logger.LogInformation($"[http close]:{httpContext.SessionId}-{httpContext.Sim}-{httpContext.ChannelNo}-{httpContext.StartTime:yyyyMMddhhmmss}"); 143 | } 144 | remove(httpContext.SessionId); 145 | } 146 | } 147 | else 148 | { 149 | try 150 | { 151 | await httpContext.HttpSendChunked(data); 152 | } 153 | catch (Exception ex) 154 | { 155 | if (Logger.IsEnabled(LogLevel.Information)) 156 | { 157 | Logger.LogInformation($"[http close]:{httpContext.SessionId}-{httpContext.Sim}-{httpContext.ChannelNo}-{httpContext.StartTime:yyyyMMddhhmmss}"); 158 | } 159 | remove(httpContext.SessionId); 160 | } 161 | } 162 | } 163 | } 164 | 165 | public int SessionCount 166 | { 167 | get 168 | { 169 | return Sessions.Count; 170 | } 171 | } 172 | 173 | public int HttpSessionCount 174 | { 175 | get 176 | { 177 | return Sessions.Count(c=>!c.Value.IsWebSocket); 178 | } 179 | } 180 | 181 | public int WebSocketSessionCount 182 | { 183 | get 184 | { 185 | return Sessions.Count(c => c.Value.IsWebSocket); 186 | } 187 | } 188 | 189 | public List GetAllBySimAndChannelNo(string sim, int channelNo) 190 | { 191 | return Sessions.Select(s => s.Value).Where(w => w.Sim == sim && w.ChannelNo == channelNo).ToList(); 192 | } 193 | 194 | public List GetAllHttpContextBySimAndChannelNo(string sim, int channelNo) 195 | { 196 | return Sessions.Select(s => s.Value).Where(w => w.Sim == sim && w.ChannelNo == channelNo&&!w.IsWebSocket).ToList(); 197 | } 198 | 199 | public List GetAll() 200 | { 201 | return Sessions.Select(s => s.Value).ToList(); 202 | } 203 | 204 | internal void TryRemoveAll() 205 | { 206 | foreach(var item in Sessions) 207 | { 208 | try 209 | { 210 | if (item.Value.IsWebSocket) 211 | { 212 | item.Value.WebSocketContext.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "server close", CancellationToken.None); 213 | } 214 | else 215 | { 216 | item.Value.Context.Response.Close(); 217 | } 218 | } 219 | catch (Exception) 220 | { 221 | 222 | } 223 | } 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Extensions/JT1078HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Metadata; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net; 6 | using System.Net.WebSockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace JT1078.Gateway.Extensions 12 | { 13 | public static class JT1078HttpContextExtensions 14 | { 15 | public static async ValueTask Http401(this HttpListenerContext context) 16 | { 17 | byte[] b = Encoding.UTF8.GetBytes("auth error"); 18 | context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 19 | context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 20 | context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; 21 | context.Response.KeepAlive = false; 22 | context.Response.ContentLength64 = b.Length; 23 | var output = context.Response.OutputStream; 24 | await output.WriteAsync(b, 0, b.Length); 25 | context.Response.OutputStream.Close(); 26 | context.Response.Close(); 27 | } 28 | 29 | public static async ValueTask Http400(this HttpListenerContext context) 30 | { 31 | byte[] b = Encoding.UTF8.GetBytes($"sim and channel parameter required."); 32 | context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 33 | context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 34 | context.Response.StatusCode = (int)HttpStatusCode.BadRequest; 35 | context.Response.KeepAlive = false; 36 | context.Response.ContentLength64 = b.Length; 37 | var output = context.Response.OutputStream; 38 | await output.WriteAsync(b, 0, b.Length); 39 | context.Response.OutputStream.Close(); 40 | context.Response.Close(); 41 | } 42 | 43 | public static void Http404(this HttpListenerContext context) 44 | { 45 | context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 46 | context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 47 | context.Response.StatusCode = (int)HttpStatusCode.NotFound; 48 | context.Response.KeepAlive = false; 49 | context.Response.OutputStream.Close(); 50 | context.Response.Close(); 51 | } 52 | 53 | public static async ValueTask Http500(this HttpListenerContext context) 54 | { 55 | byte[] b = Encoding.UTF8.GetBytes("inner error"); 56 | context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 57 | context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 58 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 59 | context.Response.KeepAlive = false; 60 | context.Response.ContentLength64 = b.Length; 61 | var output = context.Response.OutputStream; 62 | await output.WriteAsync(b, 0, b.Length); 63 | context.Response.OutputStream.Close(); 64 | context.Response.Close(); 65 | } 66 | /// 67 | /// 返回m3u8响应 68 | /// 69 | /// 70 | /// 71 | /// 72 | public static async ValueTask HttpM3U8Async(this HttpListenerContext context, Stream stream) 73 | { 74 | context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 75 | context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 76 | context.Response.ContentType = "application/x-mpegURL"; 77 | context.Response.StatusCode = (int)HttpStatusCode.OK; 78 | context.Response.ContentLength64 = stream.Length; 79 | context.Response.KeepAlive = false; 80 | await stream.CopyToAsync(context.Response.OutputStream); 81 | context.Response.OutputStream.Close(); 82 | context.Response.Close(); 83 | } 84 | /// 85 | /// 返回ts响应数 86 | /// 87 | /// 88 | /// 89 | /// 90 | public static async ValueTask HttpTsAsync(this HttpListenerContext context, Stream stream) 91 | { 92 | context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 93 | context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 94 | context.Response.ContentType = "video/MP2T"; 95 | context.Response.StatusCode = (int)HttpStatusCode.OK; 96 | context.Response.ContentLength64 = stream.Length; 97 | context.Response.KeepAlive = false; 98 | await stream.CopyToAsync(context.Response.OutputStream); 99 | context.Response.OutputStream.Close(); 100 | context.Response.Close(); 101 | } 102 | 103 | public static async ValueTask HttpSendFirstChunked(this JT1078HttpContext context, ReadOnlyMemory buffer) 104 | { 105 | context.Context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 106 | context.Context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 107 | context.Context.Response.StatusCode = (int)HttpStatusCode.OK; 108 | context.Context.Response.SendChunked = true; 109 | await context.Context.Response.OutputStream.WriteAsync(buffer); 110 | } 111 | 112 | public static async ValueTask HttpSendChunked(this JT1078HttpContext context, ReadOnlyMemory buffer) 113 | { 114 | context.Context.Response.StatusCode = (int)HttpStatusCode.OK; 115 | await context.Context.Response.OutputStream.WriteAsync(buffer); 116 | } 117 | 118 | public static async ValueTask HttpClose(this JT1078HttpContext context) 119 | { 120 | byte[] b = Encoding.UTF8.GetBytes("close"); 121 | context.Context.Response.AddHeader("Access-Control-Allow-Headers", "*"); 122 | context.Context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); 123 | context.Context.Response.StatusCode = (int)HttpStatusCode.OK; 124 | context.Context.Response.KeepAlive = false; 125 | context.Context.Response.ContentLength64 = b.Length; 126 | var output = context.Context.Response.OutputStream; 127 | await output.WriteAsync(b, 0, b.Length); 128 | context.Context.Response.OutputStream.Close(); 129 | context.Context.Response.Close(); 130 | } 131 | 132 | public static async ValueTask WebSocketClose(this JT1078HttpContext context,string content) 133 | { 134 | await context.WebSocketContext.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, content, CancellationToken.None); 135 | } 136 | 137 | public static async ValueTask WebSocketSendTextAsync(this JT1078HttpContext context, ReadOnlyMemory buffer) 138 | { 139 | await context.WebSocketContext.WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); 140 | } 141 | 142 | public static async ValueTask WebSocketSendBinaryAsync(this JT1078HttpContext context, ReadOnlyMemory buffer) 143 | { 144 | await context.WebSocketContext.WebSocket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None); 145 | } 146 | 147 | private static ReadOnlyMemory Hello = Encoding.UTF8.GetBytes("hello,jt1078"); 148 | 149 | public static async ValueTask WebSocketSendHelloAsync(this JT1078HttpContext context) 150 | { 151 | await context.WebSocketContext.WebSocket.SendAsync(Hello, WebSocketMessageType.Text, true, CancellationToken.None); 152 | } 153 | 154 | /// 155 | /// 156 | /// 157 | /// 158 | /// 159 | /// 160 | public static bool TryGetAVInfo(this HttpListenerContext context,out JT1078AVInfo jT1078AVInfo) 161 | { 162 | if (context.Request.QueryString.Count < 2) 163 | { 164 | jT1078AVInfo = default; 165 | return false; 166 | } 167 | string sim = context.Request.QueryString.Get("sim"); 168 | string channel = context.Request.QueryString.Get("channel"); 169 | if (string.IsNullOrEmpty(sim) || string.IsNullOrEmpty(channel)) 170 | { 171 | jT1078AVInfo = default; 172 | return false; 173 | } 174 | int.TryParse(channel, out int channelNo); 175 | jT1078AVInfo = new JT1078AVInfo(sim, channelNo); 176 | return true; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/Sessions/JT1078SessionManager.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Abstractions.Enums; 3 | using JT1078.Gateway.Services; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Net; 10 | using System.Net.Sockets; 11 | using System.Threading.Tasks; 12 | 13 | namespace JT1078.Gateway.Sessions 14 | { 15 | /// 16 | /// 17 | /// 不支持变态类型:既发TCP和UDP 18 | /// 19 | public class JT1078SessionManager 20 | { 21 | private readonly ILogger logger; 22 | public ConcurrentDictionary Sessions { get; } 23 | public ConcurrentDictionary TerminalPhoneNoSessions { get; } 24 | private readonly JT1078SessionNoticeService SessionNoticeService; 25 | public JT1078SessionManager( 26 | JT1078SessionNoticeService jT1078SessionNoticeService, 27 | ILoggerFactory loggerFactory) 28 | { 29 | Sessions = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); 30 | TerminalPhoneNoSessions = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); 31 | logger = loggerFactory.CreateLogger(); 32 | this.SessionNoticeService = jT1078SessionNoticeService; 33 | } 34 | 35 | public int TotalSessionCount 36 | { 37 | get 38 | { 39 | return Sessions.Count; 40 | } 41 | } 42 | 43 | public int TcpSessionCount 44 | { 45 | get 46 | { 47 | return Sessions.Where(w => w.Value.TransportProtocolType == JT1078TransportProtocolType.tcp).Count(); 48 | } 49 | } 50 | 51 | public int UdpSessionCount 52 | { 53 | get 54 | { 55 | return Sessions.Where(w => w.Value.TransportProtocolType == JT1078TransportProtocolType.udp).Count(); 56 | } 57 | } 58 | 59 | internal void TryLink(string terminalPhoneNo, IJT1078Session session) 60 | { 61 | DateTime curretDatetime= DateTime.Now; 62 | if (TerminalPhoneNoSessions.TryGetValue(terminalPhoneNo,out IJT1078Session cacheSession)) 63 | { 64 | if (session.SessionID != cacheSession.SessionID) 65 | { 66 | //从转发到直连的数据需要更新缓存 67 | session.ActiveTime = curretDatetime; 68 | TerminalPhoneNoSessions.TryUpdate(terminalPhoneNo, session, cacheSession); 69 | //会话通知 70 | SessionNoticeService.SessionNoticeBlockingCollection.Add((JT1078GatewayConstants.SessionOnline, terminalPhoneNo, session.TransportProtocolType.ToString())); 71 | } 72 | else 73 | { 74 | cacheSession.ActiveTime = curretDatetime; 75 | TerminalPhoneNoSessions.TryUpdate(terminalPhoneNo, cacheSession, cacheSession); 76 | } 77 | } 78 | else 79 | { 80 | session.TerminalPhoneNo = terminalPhoneNo; 81 | if (TerminalPhoneNoSessions.TryAdd(terminalPhoneNo, session)) 82 | { 83 | //会话通知 84 | SessionNoticeService.SessionNoticeBlockingCollection.Add((JT1078GatewayConstants.SessionOnline, terminalPhoneNo, session.TransportProtocolType.ToString())); 85 | } 86 | } 87 | } 88 | 89 | public IJT1078Session TryLink(string terminalPhoneNo, Socket socket, EndPoint remoteEndPoint) 90 | { 91 | if (TerminalPhoneNoSessions.TryGetValue(terminalPhoneNo, out IJT1078Session currentSession)) 92 | { 93 | currentSession.ActiveTime = DateTime.Now; 94 | currentSession.TerminalPhoneNo = terminalPhoneNo; 95 | currentSession.RemoteEndPoint = remoteEndPoint; 96 | TerminalPhoneNoSessions.TryUpdate(terminalPhoneNo, currentSession, currentSession); 97 | } 98 | else 99 | { 100 | JT1078UdpSession session = new JT1078UdpSession(socket); 101 | session.TerminalPhoneNo = terminalPhoneNo; 102 | session.RemoteEndPoint = remoteEndPoint; 103 | Sessions.TryAdd(session.SessionID, session); 104 | TerminalPhoneNoSessions.TryAdd(terminalPhoneNo, session); 105 | currentSession = session; 106 | } 107 | //会话通知 108 | SessionNoticeService.SessionNoticeBlockingCollection.Add((JT1078GatewayConstants.SessionOnline, terminalPhoneNo, currentSession.TransportProtocolType.ToString())); 109 | return currentSession; 110 | } 111 | 112 | internal bool TryAdd(IJT1078Session session) 113 | { 114 | return Sessions.TryAdd(session.SessionID, session); 115 | } 116 | 117 | public async ValueTask TrySendByTerminalPhoneNoAsync(string terminalPhoneNo, byte[] data) 118 | { 119 | if(TerminalPhoneNoSessions.TryGetValue(terminalPhoneNo,out var session)) 120 | { 121 | if (session.TransportProtocolType == JT1078TransportProtocolType.tcp) 122 | { 123 | await session.Client.SendAsync(data, SocketFlags.None); 124 | } 125 | else 126 | { 127 | await session.Client.SendToAsync(data, SocketFlags.None, session.RemoteEndPoint); 128 | } 129 | return true; 130 | } 131 | else 132 | { 133 | return false; 134 | } 135 | } 136 | 137 | public async ValueTask TrySendBySessionIdAsync(string sessionId, byte[] data) 138 | { 139 | if (Sessions.TryGetValue(sessionId, out var session)) 140 | { 141 | if(session.TransportProtocolType== JT1078TransportProtocolType.tcp) 142 | { 143 | await session.Client.SendAsync(data, SocketFlags.None); 144 | } 145 | else 146 | { 147 | await session.Client.SendToAsync(data, SocketFlags.None, session.RemoteEndPoint); 148 | } 149 | return true; 150 | } 151 | else 152 | { 153 | return false; 154 | } 155 | } 156 | 157 | public void RemoveByTerminalPhoneNo(string terminalPhoneNo) 158 | { 159 | if (TerminalPhoneNoSessions.TryGetValue(terminalPhoneNo, out var removeTerminalPhoneNoSessions)) 160 | { 161 | // 处理转发过来的是数据 这时候通道对设备是1对多关系,需要清理垃圾数据 162 | //1.用当前会话的通道Id找出通过转发过来的其他设备的终端号 163 | var terminalPhoneNos = TerminalPhoneNoSessions.Where(w => w.Value.SessionID == removeTerminalPhoneNoSessions.SessionID).Select(s => s.Key).ToList(); 164 | //2.存在则一个个移除 165 | string tmpTerminalPhoneNo = terminalPhoneNo; 166 | if (terminalPhoneNos.Count > 0) 167 | { 168 | //3.移除包括当前的设备号 169 | foreach (var item in terminalPhoneNos) 170 | { 171 | TerminalPhoneNoSessions.TryRemove(item, out _); 172 | } 173 | tmpTerminalPhoneNo = string.Join(",", terminalPhoneNos); 174 | } 175 | if (Sessions.TryRemove(removeTerminalPhoneNoSessions.SessionID, out var removeSession)) 176 | { 177 | removeSession.Close(); 178 | if (logger.IsEnabled(LogLevel.Information)) 179 | logger.LogInformation($"[Session Remove]:{terminalPhoneNo}-{tmpTerminalPhoneNo}"); 180 | //会话通知 181 | SessionNoticeService.SessionNoticeBlockingCollection.Add((JT1078GatewayConstants.SessionOffline, terminalPhoneNo, removeTerminalPhoneNoSessions.TransportProtocolType.ToString())); 182 | } 183 | } 184 | } 185 | 186 | public void RemoveBySessionId(string sessionId) 187 | { 188 | if (Sessions.TryRemove(sessionId, out var removeSession)) 189 | { 190 | var terminalPhoneNos = TerminalPhoneNoSessions.Where(w => w.Value.SessionID == sessionId).Select(s => s.Key).ToList(); 191 | if (terminalPhoneNos.Count > 0) 192 | { 193 | foreach (var item in terminalPhoneNos) 194 | { 195 | TerminalPhoneNoSessions.TryRemove(item, out _); 196 | } 197 | var tmpTerminalPhoneNo = string.Join(",", terminalPhoneNos); 198 | //会话通知 199 | SessionNoticeService.SessionNoticeBlockingCollection.Add((JT1078GatewayConstants.SessionOffline, tmpTerminalPhoneNo, removeSession.TransportProtocolType.ToString())); 200 | if (logger.IsEnabled(LogLevel.Information)) 201 | logger.LogInformation($"[Session Remove]:{tmpTerminalPhoneNo}"); 202 | } 203 | removeSession.Close(); 204 | } 205 | } 206 | 207 | public List GetTcpAll() 208 | { 209 | return TerminalPhoneNoSessions.Where(w => w.Value.TransportProtocolType == JT1078TransportProtocolType.tcp).Select(s => (JT1078TcpSession)s.Value).ToList(); 210 | } 211 | 212 | public List GetUdpAll() 213 | { 214 | return TerminalPhoneNoSessions.Where(w => w.Value.TransportProtocolType == JT1078TransportProtocolType.udp).Select(s => (JT1078UdpSession)s.Value).ToList(); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078HttpServer.cs: -------------------------------------------------------------------------------- 1 | using JT1078.Gateway.Abstractions; 2 | using JT1078.Gateway.Configurations; 3 | using JT1078.Gateway.Metadata; 4 | using JT1078.Gateway.Sessions; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using System; 9 | using System.Buffers; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Net; 13 | using System.Net.WebSockets; 14 | using System.Security.Principal; 15 | using System.Text; 16 | using System.Threading; 17 | using System.Threading.Tasks; 18 | using JT1078.Gateway.Extensions; 19 | using JT1078.Gateway.Services; 20 | 21 | namespace JT1078.Gateway 22 | { 23 | /// 24 | /// http服务器 25 | /// 26 | public class JT1078HttpServer : IHostedService 27 | { 28 | private readonly ILogger Logger; 29 | 30 | private readonly JT1078Configuration Configuration; 31 | 32 | private readonly IJT1078Authorization authorization; 33 | 34 | private HttpListener listener; 35 | 36 | private JT1078HttpSessionManager SessionManager; 37 | 38 | private HLSRequestManager hLSRequestManager; 39 | 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | /// 48 | public JT1078HttpServer( 49 | IOptions jT1078ConfigurationAccessor, 50 | IJT1078Authorization authorization, 51 | JT1078HttpSessionManager sessionManager, 52 | HLSRequestManager hLSRequestManager, 53 | ILoggerFactory loggerFactory) 54 | { 55 | Logger = loggerFactory.CreateLogger(); 56 | Configuration = jT1078ConfigurationAccessor.Value; 57 | this.authorization = authorization; 58 | this.SessionManager = sessionManager; 59 | this.hLSRequestManager = hLSRequestManager; 60 | } 61 | 62 | /// 63 | /// 64 | /// 65 | /// 66 | /// 67 | public Task StartAsync(CancellationToken cancellationToken) 68 | { 69 | if (!HttpListener.IsSupported) 70 | { 71 | Logger.LogWarning("Windows XP SP2 or Server 2003 is required to use the HttpListener class."); 72 | return Task.CompletedTask; 73 | } 74 | listener = new HttpListener(); 75 | listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous; 76 | try 77 | { 78 | listener.Prefixes.Add($"http://*:{Configuration.HttpPort}/"); 79 | listener.Start(); 80 | } 81 | catch (System.Net.HttpListenerException ex) 82 | { 83 | Logger.LogWarning(ex, $"{ex.Message}:使用cmd命令[netsh http add urlacl url=http://*:{Configuration.HttpPort}/ user=Everyone]"); 84 | return Task.CompletedTask; 85 | } 86 | Logger.LogInformation($"JT1078 Http Server start at {IPAddress.Any}:{Configuration.HttpPort}."); 87 | Task.Factory.StartNew(async () => 88 | { 89 | while (listener.IsListening) 90 | { 91 | var context = await listener.GetContextAsync(); 92 | try 93 | { 94 | await Task.Run(async () => 95 | { 96 | if (Logger.IsEnabled(LogLevel.Information)) 97 | { 98 | Logger.LogInformation($"[Http RequestTraceIdentifier]:{context.Request.RequestTraceIdentifier.ToString()}-{context.Request.RemoteEndPoint.ToString()}-{context.Request.RawUrl}"); 99 | } 100 | if (context.Request.RawUrl.StartsWith("/favicon.ico")) 101 | { 102 | context.Http404(); 103 | return; 104 | } 105 | if (!context.TryGetAVInfo(out JT1078AVInfo jT1078AVInfo)) 106 | { 107 | await context.Http400(); 108 | return; 109 | } 110 | if (context.Request.RawUrl.Contains(".m3u8")) 111 | { 112 | ProcessM3u8(context, jT1078AVInfo); 113 | } 114 | else if (context.Request.RawUrl.Contains(".ts")) 115 | { 116 | ProcessTs(context, jT1078AVInfo); 117 | } 118 | else if (context.Request.RawUrl.Contains(".flv")) 119 | { 120 | ProcessFlv(context, jT1078AVInfo); 121 | } 122 | else if (context.Request.RawUrl.Contains(".mp4")) 123 | { 124 | ProcessFMp4(context, jT1078AVInfo); 125 | } 126 | else 127 | { 128 | await context.Http401(); 129 | } 130 | }); 131 | } 132 | catch (Exception ex) 133 | { 134 | await context.Http500(); 135 | Logger.LogError(ex, $"[Http RequestTraceIdentifier]:{context.Request.RequestTraceIdentifier.ToString()}-{context.Request.RemoteEndPoint.ToString()}-{context.Request.RawUrl}-{ex.StackTrace}"); 136 | } 137 | } 138 | }, cancellationToken); 139 | return Task.CompletedTask; 140 | } 141 | 142 | private void ProcessM3u8(HttpListenerContext context, JT1078AVInfo jT1078AVInfo) 143 | { 144 | if (authorization.Authorization(context, out IPrincipal principal)) 145 | { 146 | hLSRequestManager.HandleHlsRequest(context, principal, jT1078AVInfo); 147 | } 148 | } 149 | 150 | private void ProcessTs(HttpListenerContext context, JT1078AVInfo jT1078AVInfo) 151 | { 152 | //ts 无需验证 153 | hLSRequestManager.HandleHlsRequest(context, default, jT1078AVInfo); 154 | } 155 | 156 | private async void ProcessFlv(HttpListenerContext context, JT1078AVInfo jT1078AVInfo) 157 | { 158 | if (authorization.Authorization(context, out IPrincipal principal)) 159 | { 160 | if (context.Request.IsWebSocketRequest) 161 | { 162 | await ProccessWebSocket(context, principal, jT1078AVInfo, RTPVideoType.Ws_Flv); 163 | } 164 | else 165 | { 166 | ProccessHttpKeepLive(context, principal, jT1078AVInfo, RTPVideoType.Http_Flv); 167 | } 168 | } 169 | } 170 | 171 | private async void ProcessFMp4(HttpListenerContext context, JT1078AVInfo jT1078AVInfo) 172 | { 173 | if (authorization.Authorization(context, out IPrincipal principal)) 174 | { 175 | if (context.Request.IsWebSocketRequest) 176 | { 177 | await ProccessWebSocket(context, principal, jT1078AVInfo, RTPVideoType.Ws_FMp4); 178 | } 179 | else 180 | { 181 | ProccessHttpKeepLive(context, principal, jT1078AVInfo, RTPVideoType.Http_FMp4); 182 | } 183 | } 184 | } 185 | 186 | private async ValueTask ProccessWebSocket(HttpListenerContext context, IPrincipal principal, JT1078AVInfo jT1078AVInfo, RTPVideoType videoType) 187 | { 188 | HttpListenerWebSocketContext wsContext = await context.AcceptWebSocketAsync(null, keepAliveInterval: TimeSpan.FromSeconds(5)); 189 | var jT1078HttpContext = new JT1078HttpContext(context, wsContext, principal); 190 | jT1078HttpContext.Sim = jT1078AVInfo.Sim; 191 | jT1078HttpContext.ChannelNo = jT1078AVInfo.ChannelNo; 192 | jT1078HttpContext.RTPVideoType = videoType; 193 | SessionManager.TryAdd(jT1078HttpContext); 194 | //这个发送出去,flv.js就报错了 195 | //await jT1078HttpContext.WebSocketSendHelloAsync(); 196 | await Task.Factory.StartNew(async (state) => 197 | { 198 | //https://www.bejson.com/httputil/websocket/ 199 | //ws://localhost:15555?token=22&sim=1221&channel=1 200 | var websocketContext = state as JT1078HttpContext; 201 | while (websocketContext.WebSocketContext.WebSocket.State == WebSocketState.Open || 202 | websocketContext.WebSocketContext.WebSocket.State == WebSocketState.Connecting) 203 | { 204 | var buffer = ArrayPool.Shared.Rent(256); 205 | try 206 | { 207 | //客户端主动断开需要有个线程去接收通知,不然会客户端会卡死直到超时 208 | WebSocketReceiveResult receiveResult = await websocketContext.WebSocketContext.WebSocket.ReceiveAsync(buffer, CancellationToken.None); 209 | if (receiveResult.EndOfMessage) 210 | { 211 | if (receiveResult.Count > 0) 212 | { 213 | var data = buffer.AsSpan().Slice(0, receiveResult.Count).ToArray(); 214 | if (Logger.IsEnabled(LogLevel.Trace)) 215 | { 216 | Logger.LogTrace($"[ws receive]:{Encoding.UTF8.GetString(data)}"); 217 | } 218 | await websocketContext.WebSocketSendTextAsync(data); 219 | } 220 | } 221 | } 222 | finally 223 | { 224 | ArrayPool.Shared.Return(buffer); 225 | } 226 | } 227 | if (Logger.IsEnabled(LogLevel.Information)) 228 | { 229 | Logger.LogInformation($"[ws close]:{websocketContext.SessionId}-{websocketContext.Sim}-{websocketContext.ChannelNo}-{websocketContext.StartTime:yyyyMMddhhmmss}-{websocketContext.Context.Request.RawUrl}"); 230 | } 231 | SessionManager.TryRemove(websocketContext.SessionId); 232 | }, jT1078HttpContext); 233 | } 234 | 235 | private void ProccessHttpKeepLive(HttpListenerContext context, IPrincipal principal, JT1078AVInfo jT1078AVInfo, RTPVideoType videoType) 236 | { 237 | var jT1078HttpContext = new JT1078HttpContext(context, principal); 238 | jT1078HttpContext.RTPVideoType = videoType; 239 | jT1078HttpContext.Sim = jT1078AVInfo.Sim; 240 | jT1078HttpContext.ChannelNo = jT1078AVInfo.ChannelNo; 241 | SessionManager.TryAdd(jT1078HttpContext); 242 | } 243 | 244 | /// 245 | /// 246 | /// 247 | /// 248 | /// 249 | public Task StopAsync(CancellationToken cancellationToken) 250 | { 251 | try 252 | { 253 | Logger.LogInformation($"JT1078 Http Server stop at {IPAddress.Any}:{Configuration.HttpPort}."); 254 | SessionManager.TryRemoveAll(); 255 | listener.Stop(); 256 | } 257 | catch (System.ObjectDisposedException ex) 258 | { 259 | 260 | } 261 | catch (Exception ex) 262 | { 263 | Logger.LogError(ex, $"JT1078 Http Server error at {IPAddress.Any}:{Configuration.HttpPort}."); 264 | } 265 | return Task.CompletedTask; 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078.Gateway.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JT1078.Gateway 5 | 6 | 7 | 8 | 9 | Tcp读超时 10 | 默认10分钟检查一次 11 | 12 | 13 | 14 | 15 | Tcp 60s检查一次 16 | 17 | 18 | 19 | 20 | Udp读超时 21 | 22 | 23 | 24 | 25 | Udp 60s检查一次 26 | 27 | 28 | 29 | 30 | Hls根目录 31 | 32 | 33 | 34 | 35 | 协调器发送心跳时间 36 | 默认60s发送一次 37 | 38 | 39 | 40 | 41 | 协调器Coordinator主机 42 | http://localhost/ 43 | http://127.0.0.1/ 44 | 45 | 46 | 47 | 48 | 协调器Coordinator主机登录账号 49 | 50 | 51 | 52 | 53 | 协调器Coordinator主机登录密码 54 | 55 | 56 | 57 | 58 | 返回m3u8响应 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 返回ts响应数 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 清理hls session 83 | 84 | 85 | 86 | 87 | 协调器客户端 88 | 89 | 90 | 91 | 92 | 登录 93 | 94 | 95 | 96 | 97 | 发送重制至协调器中 98 | 99 | 100 | 101 | 102 | 发送心跳至协调器中 103 | 104 | 105 | 106 | 107 | 108 | 发送设备号和通道给协调器中 109 | 110 | 111 | 112 | 113 | 114 | 115 | http服务器 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 使用队列方式 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 使用队列方式 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 音视频信息 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | sim 175 | 176 | 177 | 178 | 179 | 通道号 180 | 181 | 182 | 183 | 184 | key 185 | 186 | 187 | 188 | 189 | 190 | http上下文 191 | 192 | 193 | 194 | 195 | 会话Id 196 | 197 | 198 | 199 | 200 | http上下文 201 | 202 | 203 | 204 | 205 | ws上下文 206 | 207 | 208 | 209 | 210 | 用户信息 211 | 212 | 213 | 214 | 215 | 观看视频类型 216 | 217 | 218 | 219 | 220 | 是否是ws协议 221 | 222 | 223 | 224 | 225 | 开始时间 226 | 227 | 228 | 229 | 230 | 是否发送首包视频数据 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 观看视频类型 251 | 252 | 253 | 254 | 255 | Http_Flv 256 | 257 | 258 | 259 | 260 | Ws_Flv 261 | 262 | 263 | 264 | 265 | Http_Hls 266 | 267 | 268 | 269 | 270 | Http_FMp4 271 | 272 | 273 | 274 | 275 | Ws_FMp4 276 | 277 | 278 | 279 | 280 | hls路径是否存在处理,及文件监控处理 281 | 282 | 283 | 284 | 285 | 添加路径 286 | 287 | 288 | 289 | 290 | 291 | 判断路径是否存在 292 | 293 | 294 | 295 | 296 | 297 | 298 | 移除所有路径 299 | 300 | 301 | 302 | 303 | 304 | 是否存在文件监控 305 | 306 | 307 | 308 | 309 | 310 | 311 | 添加文件监控 312 | 313 | 314 | 315 | 316 | 317 | 318 | 删除文件监控 319 | 320 | 321 | 322 | 323 | 324 | Hls请求管理 325 | 326 | 327 | 328 | 329 | 处理hls实时视频请求 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 发送音视频数据 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 不支持变态类型:既发TCP和UDP 347 | 348 | 349 | 350 | 351 | 终端手机号 352 | 353 | 354 | 355 | 356 | 终端手机号 357 | 358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /src/JT1078.Gateway/JT1078TcpServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.IO.Pipelines; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Security.Cryptography; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using JT1078.Gateway.Abstractions; 13 | using JT1078.Gateway.Configurations; 14 | using JT1078.Gateway.Sessions; 15 | using JT1078.Protocol; 16 | using JT1078.Protocol.Enums; 17 | using JT1078.Protocol.Extensions; 18 | using Microsoft.Extensions.Hosting; 19 | using Microsoft.Extensions.Logging; 20 | using Microsoft.Extensions.Options; 21 | 22 | namespace JT1078.Gateway 23 | { 24 | public class JT1078TcpServer : IHostedService 25 | { 26 | private Socket server; 27 | 28 | private readonly ILogger Logger; 29 | 30 | private readonly ILogger LogLogger; 31 | 32 | private readonly JT1078Configuration Configuration; 33 | 34 | private readonly JT1078SessionManager SessionManager; 35 | 36 | private readonly IJT1078MsgProducer jT1078MsgProducer; 37 | 38 | private long Counter = 0; 39 | 40 | /// 41 | /// 使用队列方式 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | public JT1078TcpServer( 48 | IJT1078MsgProducer jT1078MsgProducer, 49 | IOptions jT1078ConfigurationAccessor, 50 | ILoggerFactory loggerFactory, 51 | JT1078SessionManager jT1078SessionManager) 52 | { 53 | SessionManager = jT1078SessionManager; 54 | Logger = loggerFactory.CreateLogger(); 55 | LogLogger = loggerFactory.CreateLogger("JT1078.Gateway.JT1078Logging"); 56 | Configuration = jT1078ConfigurationAccessor.Value; 57 | this.jT1078MsgProducer = jT1078MsgProducer; 58 | InitServer(); 59 | } 60 | 61 | private void InitServer() 62 | { 63 | var IPEndPoint = new System.Net.IPEndPoint(IPAddress.Any, Configuration.TcpPort); 64 | server = new Socket(IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 65 | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); 66 | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); 67 | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, Configuration.MiniNumBufferSize); 68 | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, Configuration.MiniNumBufferSize); 69 | server.LingerState = new LingerOption(false, 0); 70 | server.Bind(IPEndPoint); 71 | server.Listen(Configuration.SoBacklog); 72 | } 73 | public Task StartAsync(CancellationToken cancellationToken) 74 | { 75 | Logger.LogInformation($"JT1078 Tcp Server start at {IPAddress.Any}:{Configuration.TcpPort}."); 76 | Task.Factory.StartNew(async () => 77 | { 78 | while (!cancellationToken.IsCancellationRequested) 79 | { 80 | var socket = await server.AcceptAsync(); 81 | JT1078TcpSession jT808TcpSession = new JT1078TcpSession(socket); 82 | SessionManager.TryAdd(jT808TcpSession); 83 | await Task.Factory.StartNew(async (state) => 84 | { 85 | var session = (JT1078TcpSession)state; 86 | if (Logger.IsEnabled(LogLevel.Information)) 87 | { 88 | Logger.LogInformation($"[Connected]:{session.Client.RemoteEndPoint}"); 89 | } 90 | var pipe = new Pipe(); 91 | Task writing = FillPipeAsync(session, pipe.Writer); 92 | Task reading = ReadPipeAsync(session, pipe.Reader); 93 | await Task.WhenAll(reading, writing); 94 | SessionManager.RemoveBySessionId(session.SessionID); 95 | }, jT808TcpSession); 96 | } 97 | }, cancellationToken); 98 | return Task.CompletedTask; 99 | } 100 | private async Task FillPipeAsync(JT1078TcpSession session, PipeWriter writer) 101 | { 102 | while (true) 103 | { 104 | try 105 | { 106 | Memory memory = writer.GetMemory(Configuration.MiniNumBufferSize); 107 | //设备多久没发数据就断开连接 Receive Timeout. 108 | int bytesRead = await session.Client.ReceiveAsync(memory, SocketFlags.None, session.ReceiveTimeout.Token); 109 | if (bytesRead == 0) 110 | { 111 | break; 112 | } 113 | writer.Advance(bytesRead); 114 | } 115 | catch (System.ObjectDisposedException ex) 116 | { 117 | 118 | } 119 | catch (OperationCanceledException ex) 120 | { 121 | Logger.LogError($"[Receive Timeout]:{session.Client.RemoteEndPoint}"); 122 | break; 123 | } 124 | catch (System.Net.Sockets.SocketException ex) 125 | { 126 | Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message}]:{session.Client.RemoteEndPoint}"); 127 | break; 128 | } 129 | #pragma warning disable CA1031 // Do not catch general exception types 130 | catch (Exception ex) 131 | { 132 | Logger.LogError(ex, $"[Receive Error]:{session.Client.RemoteEndPoint}"); 133 | break; 134 | } 135 | #pragma warning restore CA1031 // Do not catch general exception types 136 | FlushResult result = await writer.FlushAsync(); 137 | if (result.IsCompleted) 138 | { 139 | break; 140 | } 141 | } 142 | writer.Complete(); 143 | } 144 | private async Task ReadPipeAsync(JT1078TcpSession session, PipeReader reader) 145 | { 146 | FixedHeaderInfo fixedHeaderInfo = new FixedHeaderInfo(); 147 | while (true) 148 | { 149 | ReadResult result = await reader.ReadAsync(); 150 | if (result.IsCompleted) 151 | { 152 | break; 153 | } 154 | ReadOnlySequence buffer = result.Buffer; 155 | SequencePosition consumed = buffer.Start; 156 | SequencePosition examined = buffer.End; 157 | try 158 | { 159 | if (result.IsCanceled) break; 160 | if (buffer.Length > 0) 161 | { 162 | ReaderBuffer(ref buffer, fixedHeaderInfo, session, out consumed, out examined); 163 | } 164 | } 165 | #pragma warning disable CA1031 // Do not catch general exception types 166 | catch (Exception ex) 167 | { 168 | Logger.LogError(ex, $"[ReadPipe Error]:{session.Client.RemoteEndPoint}"); 169 | break; 170 | } 171 | #pragma warning restore CA1031 // Do not catch general exception types 172 | finally 173 | { 174 | reader.AdvanceTo(consumed, examined); 175 | } 176 | } 177 | reader.Complete(); 178 | } 179 | private void ReaderBuffer(ref ReadOnlySequence buffer, FixedHeaderInfo fixedHeaderInfo, JT1078TcpSession session, out SequencePosition consumed, out SequencePosition examined) 180 | { 181 | consumed = buffer.Start; 182 | examined = buffer.End; 183 | SequenceReader seqReader = new SequenceReader(buffer); 184 | long totalConsumed = 0; 185 | while (!seqReader.End) 186 | { 187 | if (seqReader.Length < 30) 188 | { 189 | fixedHeaderInfo.Reset(); 190 | break; 191 | } 192 | if (!fixedHeaderInfo.FoundHeader) 193 | { 194 | var header = seqReader.Sequence.Slice(0, 4); 195 | uint headerValue = BinaryPrimitives.ReadUInt32BigEndian(header.ToArray()); 196 | if (JT1078Package.FH == headerValue) 197 | { 198 | //sim 199 | if (string.IsNullOrEmpty(fixedHeaderInfo.SIM)) 200 | { 201 | fixedHeaderInfo.SIM = ReadBCD(seqReader.Sequence.Slice(8, 6).ToArray(), 12); 202 | fixedHeaderInfo.SIM = fixedHeaderInfo.SIM ?? session.SessionID; 203 | } 204 | //根据数据类型处理对应的数据长度 205 | fixedHeaderInfo.TotalSize += 15; 206 | var dataType = seqReader.Sequence.Slice(fixedHeaderInfo.TotalSize, 1).FirstSpan[0]; 207 | fixedHeaderInfo.TotalSize += 1; 208 | int bodyLength = GetRealDataBodyLength(dataType); 209 | fixedHeaderInfo.TotalSize += bodyLength; 210 | var bodyLengthFirstSpan = seqReader.Sequence.Slice(fixedHeaderInfo.TotalSize, 2).ToArray(); 211 | fixedHeaderInfo.TotalSize += 2; 212 | //数据体长度 213 | bodyLength = BinaryPrimitives.ReadUInt16BigEndian(bodyLengthFirstSpan); 214 | if (bodyLength < 0) 215 | { 216 | fixedHeaderInfo.Reset(); 217 | throw new ArgumentException("jt1078 package body length Error."); 218 | } 219 | if (bodyLength == 0)//数据体长度为0 220 | { 221 | var package1 = seqReader.Sequence.Slice(0, fixedHeaderInfo.TotalSize).ToArray(); 222 | seqReader.Advance(fixedHeaderInfo.TotalSize); 223 | if (LogLogger.IsEnabled(LogLevel.Trace)) 224 | { 225 | LogLogger.LogTrace($"{package1.ToHexString()}"); 226 | } 227 | try 228 | { 229 | SessionManager.TryLink(fixedHeaderInfo.SIM, session); 230 | jT1078MsgProducer.ProduceAsync(fixedHeaderInfo.SIM, package1.ToArray()); 231 | } 232 | catch (Exception ex) 233 | { 234 | LogLogger.LogError($"[Error Parse 1]:{fixedHeaderInfo.SIM}-{package1.ToHexString()}"); 235 | Logger.LogError(ex, $"[Error Parse 1]:{fixedHeaderInfo.SIM}-{package1.ToHexString()}"); 236 | } 237 | finally 238 | { 239 | totalConsumed += seqReader.Consumed; 240 | seqReader = new SequenceReader(seqReader.Sequence.Slice(fixedHeaderInfo.TotalSize)); 241 | fixedHeaderInfo.Reset(); 242 | #if DEBUG 243 | Interlocked.Increment(ref Counter); 244 | #endif 245 | } 246 | continue; 247 | } 248 | //数据体 249 | fixedHeaderInfo.TotalSize += bodyLength; 250 | fixedHeaderInfo.FoundHeader = true; 251 | } 252 | else 253 | { 254 | fixedHeaderInfo.Reset(); 255 | throw new ArgumentException("not JT1078 package."); 256 | } 257 | } 258 | if ((seqReader.Length - fixedHeaderInfo.TotalSize) < 0) break; 259 | var package = seqReader.Sequence.Slice(0, fixedHeaderInfo.TotalSize).ToArray(); 260 | seqReader.Advance(fixedHeaderInfo.TotalSize); 261 | if (LogLogger.IsEnabled(LogLevel.Trace)) 262 | { 263 | LogLogger.LogTrace($"===>{fixedHeaderInfo.SIM}-{package.ToHexString()}"); 264 | } 265 | try 266 | { 267 | SessionManager.TryLink(fixedHeaderInfo.SIM, session); 268 | jT1078MsgProducer.ProduceAsync(fixedHeaderInfo.SIM, package); 269 | } 270 | catch (Exception ex) 271 | { 272 | LogLogger.LogError($"[Error Parse 2]:{fixedHeaderInfo.SIM}-{package.ToHexString()}"); 273 | Logger.LogError(ex, $"[Error Parse 2]:{fixedHeaderInfo.SIM}-{package.ToHexString()}"); 274 | } 275 | finally 276 | { 277 | totalConsumed += seqReader.Consumed; 278 | seqReader = new SequenceReader(seqReader.Sequence.Slice(fixedHeaderInfo.TotalSize)); 279 | fixedHeaderInfo.Reset(); 280 | #if DEBUG 281 | Interlocked.Increment(ref Counter); 282 | #endif 283 | } 284 | #if DEBUG 285 | if (Logger.IsEnabled(LogLevel.Trace)) 286 | { 287 | Logger.LogTrace($"======>{Counter}"); 288 | } 289 | #endif 290 | } 291 | if (seqReader.End) 292 | { 293 | examined = consumed = buffer.End; 294 | } 295 | else 296 | { 297 | consumed = buffer.GetPosition(totalConsumed); 298 | } 299 | } 300 | public Task StopAsync(CancellationToken cancellationToken) 301 | { 302 | Logger.LogInformation("JT1078 Tcp Server Stop"); 303 | SessionManager.GetTcpAll().ForEach(session => 304 | { 305 | try 306 | { 307 | session.Close(); 308 | } 309 | catch (Exception ex) 310 | { 311 | 312 | } 313 | }); 314 | if (server?.Connected ?? false) 315 | server.Shutdown(SocketShutdown.Both); 316 | server?.Close(); 317 | return Task.CompletedTask; 318 | } 319 | string ReadBCD(ReadOnlySpan readOnlySpan, int len) 320 | { 321 | int count = len / 2; 322 | StringBuilder bcdSb = new StringBuilder(count); 323 | for (int i = 0; i < count; i++) 324 | { 325 | bcdSb.Append(readOnlySpan[i].ToString("X2")); 326 | } 327 | return bcdSb.ToString().TrimStart('0'); 328 | } 329 | int GetRealDataBodyLength(byte dataType) 330 | { 331 | JT1078Label3 label3 = new JT1078Label3(dataType); 332 | int bodyLength = 0; 333 | //透传的时候没有该字段 334 | if (label3.DataType != JT1078DataType.透传数据) 335 | { 336 | //时间戳 337 | bodyLength += 8; 338 | } 339 | //非视频帧时没有该字段 340 | if (label3.DataType == JT1078DataType.视频I帧 || 341 | label3.DataType == JT1078DataType.视频P帧 || 342 | label3.DataType == JT1078DataType.视频B帧) 343 | { 344 | //上一个关键帧 + 上一帧 = 2 + 2 345 | bodyLength += 4; 346 | } 347 | return bodyLength; 348 | } 349 | class FixedHeaderInfo 350 | { 351 | public bool FoundHeader { get; set; } 352 | public int TotalSize { get; set; } 353 | public string SIM { get; set; } 354 | public void Reset() 355 | { 356 | FoundHeader = false; 357 | TotalSize = 0; 358 | } 359 | } 360 | } 361 | } 362 | --------------------------------------------------------------------------------