1- // ==========================================================================================
1+ // ==========================================================================================
22// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
33// GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
44// 均受中华人民共和国及相关国际法律法规保护。
3131
3232using GameFrameX . Apps . Server . Component ;
3333using GameFrameX . Apps . Server . Entity ;
34+ using GameFrameX . Apps . Common . Event ;
35+ using GameFrameX . Apps . Common . EventData ;
3436using GameFrameX . Core . Abstractions . Attribute ;
3537using GameFrameX . Core . Abstractions . Events ;
38+ using GameFrameX . Core . Events ;
3639using GameFrameX . Core . Timer . Handler ;
3740using GameFrameX . Foundation . Utility ;
3841using GameFrameX . Hotfix . Logic . Player . Login ;
42+ using System . Collections ;
43+ using System . Security . Cryptography ;
44+ using System . Text ;
3945
4046namespace GameFrameX . Hotfix . Logic . Server ;
4147
4248public class ServerComponentAgent : StateComponentAgent < ServerComponent , ServerState >
4349{
50+ private readonly Dictionary < string , HashSet < long > > _topologySnapshot = new ( StringComparer . OrdinalIgnoreCase ) ;
51+ private readonly Dictionary < string , int > _offlineConfirmCounter = new ( StringComparer . OrdinalIgnoreCase ) ;
52+ private static readonly string [ ] TopologyServiceNames = { GlobalConst . GameServiceName , GlobalConst . SocialServiceName , GlobalConst . GatewayServiceName } ;
53+
4454 public override async Task < bool > Active ( )
4555 {
4656 var isContinue = await base . Active ( ) ;
4757 if ( isContinue )
4858 {
4959 // 跨天定时器
5060 WithCronExpression < CrossDayTimeHandler > ( "0 0 0 * * ? *" ) ;
61+ Schedule < ServiceTopologyWatcherHandler > ( TimeSpan . FromSeconds ( 5 ) , TimeSpan . FromSeconds ( 5 ) ) ;
5162 if ( State . FirstStartTime == default )
5263 {
5364 State . FirstStartTime = TimerHelper . UnixTimeSeconds ( ) ;
@@ -57,6 +68,120 @@ public override async Task<bool> Active()
5768
5869 return isContinue ;
5970 }
71+
72+ private Task WatchServiceTopology ( )
73+ {
74+ var currentSnapshot = BuildCurrentTopologySnapshot ( ) ;
75+ foreach ( var currentPair in currentSnapshot )
76+ {
77+ var serviceName = currentPair . Key ;
78+ var currentInstances = currentPair . Value ;
79+ _topologySnapshot . TryGetValue ( serviceName , out var previousInstances ) ;
80+ previousInstances ??= new HashSet < long > ( ) ;
81+ foreach ( var instance in currentInstances )
82+ {
83+ if ( ! previousInstances . Contains ( instance . Key ) )
84+ {
85+ EventDispatcher . Dispatch ( ActorId , ( int ) EventId . ServiceOnline , new ServiceOnlineEventArgs ( serviceName , instance . Key , DateTime . UtcNow , instance . Value ) ) ;
86+ }
87+
88+ _offlineConfirmCounter . Remove ( GetOfflineKey ( serviceName , instance . Key ) ) ;
89+ }
90+
91+ foreach ( var instanceId in previousInstances )
92+ {
93+ if ( currentInstances . ContainsKey ( instanceId ) )
94+ {
95+ continue ;
96+ }
97+
98+ var counterKey = GetOfflineKey ( serviceName , instanceId ) ;
99+ _offlineConfirmCounter . TryGetValue ( counterKey , out var counter ) ;
100+ counter ++ ;
101+ if ( counter >= 2 )
102+ {
103+ EventDispatcher . Dispatch ( ActorId , ( int ) EventId . ServiceOffline , new ServiceOfflineEventArgs ( serviceName , instanceId , "Removed" , DateTime . UtcNow ) ) ;
104+ _offlineConfirmCounter . Remove ( counterKey ) ;
105+ continue ;
106+ }
107+
108+ _offlineConfirmCounter [ counterKey ] = counter ;
109+ }
110+ }
111+
112+ _topologySnapshot . Clear ( ) ;
113+ foreach ( var pair in currentSnapshot )
114+ {
115+ _topologySnapshot [ pair . Key ] = pair . Value . Keys . ToHashSet ( ) ;
116+ }
117+
118+ return Task . CompletedTask ;
119+ }
120+
121+ private static string GetOfflineKey ( string serviceName , long instanceId )
122+ {
123+ return $ "{ serviceName } :{ instanceId } ";
124+ }
125+
126+ private static Dictionary < string , Dictionary < long , IReadOnlyList < string > > > BuildCurrentTopologySnapshot ( )
127+ {
128+ var snapshot = new Dictionary < string , Dictionary < long , IReadOnlyList < string > > > ( StringComparer . OrdinalIgnoreCase ) ;
129+ foreach ( var serviceName in TopologyServiceNames )
130+ {
131+ snapshot [ serviceName ] = new Dictionary < long , IReadOnlyList < string > > ( ) ;
132+ }
133+
134+ var variables = Environment . GetEnvironmentVariables ( ) ;
135+ foreach ( DictionaryEntry variable in variables )
136+ {
137+ if ( variable . Key is not string rawKey || variable . Value is not string rawValue || string . IsNullOrWhiteSpace ( rawValue ) )
138+ {
139+ continue ;
140+ }
141+
142+ var key = rawKey . Replace ( "__" , ":" , StringComparison . Ordinal ) . Trim ( ) ;
143+ if ( ! key . StartsWith ( "services:" , StringComparison . OrdinalIgnoreCase ) )
144+ {
145+ continue ;
146+ }
147+
148+ var segments = key . Split ( ':' , StringSplitOptions . RemoveEmptyEntries ) ;
149+ if ( segments . Length < 3 )
150+ {
151+ continue ;
152+ }
153+
154+ var serviceToken = segments [ 1 ] ;
155+ var serviceName = TopologyServiceNames . FirstOrDefault ( name => string . Equals ( name , serviceToken , StringComparison . OrdinalIgnoreCase ) ) ;
156+ if ( serviceName == null )
157+ {
158+ continue ;
159+ }
160+
161+ if ( GlobalSettings . CurrentSetting != null && string . Equals ( serviceName , GlobalSettings . CurrentSetting . ServerType , StringComparison . OrdinalIgnoreCase ) )
162+ {
163+ continue ;
164+ }
165+
166+ var instanceId = BuildInstanceId ( serviceName , rawValue ) ;
167+ if ( ! snapshot [ serviceName ] . TryGetValue ( instanceId , out var endpoints ) )
168+ {
169+ endpoints = Array . Empty < string > ( ) ;
170+ snapshot [ serviceName ] [ instanceId ] = endpoints ;
171+ }
172+
173+ snapshot [ serviceName ] [ instanceId ] = endpoints . Concat ( new [ ] { rawValue } ) . Distinct ( StringComparer . OrdinalIgnoreCase ) . ToArray ( ) ;
174+ }
175+
176+ return snapshot ;
177+ }
178+
179+ private static long BuildInstanceId ( string serviceName , string endpoint )
180+ {
181+ var payload = $ "{ serviceName } |{ endpoint } ";
182+ var hash = SHA256 . HashData ( Encoding . UTF8 . GetBytes ( payload ) ) ;
183+ return Math . Abs ( BitConverter . ToInt64 ( hash , 0 ) ) ;
184+ }
60185
61186 [ Service ]
62187 [ Discard ]
@@ -207,5 +332,13 @@ protected override async Task HandleTimer(ServerComponentAgent agent, GameEventA
207332 await ActorManager . CrossDay ( 1 , GlobalConst . ActorTypeServer ) ;
208333 }
209334 }
335+
336+ private class ServiceTopologyWatcherHandler : TimerHandler < ServerComponentAgent >
337+ {
338+ protected override Task HandleTimer ( ServerComponentAgent agent , GameEventArgs gameEventArgs )
339+ {
340+ return agent . WatchServiceTopology ( ) ;
341+ }
342+ }
210343 /*******************演示代码**************************/
211- }
344+ }
0 commit comments