├── 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 |  |  |  |
25 | | Install-Package JT1078.Gateway |  | | |
26 | | Install-Package JT1078.Gateway.InMemoryMQ |  |  |  |
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 |
--------------------------------------------------------------------------------