1+ // ==========================================================================================
2+ // GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+ // GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+ // 均受中华人民共和国及相关国际法律法规保护。
5+ // are protected by the laws of the People's Republic of China and relevant international regulations.
6+ //
7+ // 使用本项目须严格遵守相应法律法规及开源许可证之规定。
8+ // Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
9+ //
10+ // 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
11+ // This project is dual-licensed under the MIT License and Apache License 2.0,
12+ // 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
13+ // please refer to the LICENSE file in the root directory of the source code for the full license text.
14+ //
15+ // 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
16+ // It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
17+ // 侵犯他人合法权益等法律法规所禁止的行为!
18+ // or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
19+ // 因基于本项目二次开发所产生的一切法律纠纷与责任,
20+ // Any legal disputes and liabilities arising from secondary development based on this project
21+ // 本项目组织与贡献者概不承担。
22+ // shall be borne solely by the developer; the project organization and contributors assume no responsibility.
23+ //
24+ // GitHub 仓库:https://github.com/GameFrameX
25+ // GitHub Repository: https://github.com/GameFrameX
26+ // Gitee 仓库:https://gitee.com/GameFrameX
27+ // Gitee Repository: https://gitee.com/GameFrameX
28+ // 官方文档:https://gameframex.doc.alianblank.com/
29+ // Official Documentation: https://gameframex.doc.alianblank.com/
30+ // ==========================================================================================
31+
32+ using System . Net ;
33+ using GameFrameX . Foundation . Extensions ;
34+ using GameFrameX . Foundation . Logger ;
35+ using GameFrameX . NetWork ;
36+ using GameFrameX . NetWork . Abstractions ;
37+ using GameFrameX . NetWork . Messages ;
38+ using GameFrameX . SuperSocket . ClientEngine ;
39+
40+ namespace GameFrameX . StartUp . ServiceClient ;
41+
42+ /// <summary>
43+ /// 游戏程序TCP客户端类,用于处理与服务器的TCP连接和消息收发
44+ /// </summary>
45+ internal sealed class GameAppServiceClient : IDisposable
46+ {
47+ /// <summary>
48+ /// 内部TCP会话实例,负责底层网络通信
49+ /// </summary>
50+ private readonly AsyncTcpSession _mTcpClient ;
51+
52+ /// <summary>
53+ /// 当前重连次数计数器
54+ /// </summary>
55+ public int RetryCount { get ; private set ; }
56+
57+ /// <summary>
58+ /// RPC会话实例,用于处理RPC请求和响应
59+ /// </summary>
60+ private readonly IRpcSession _rpcSession ;
61+
62+ /// <summary>
63+ /// 服务器终结点(IP与端口)
64+ /// </summary>
65+ private readonly EndPoint _serverHost ;
66+
67+ /// <summary>
68+ /// 获取服务器终结点(IP与端口)
69+ /// </summary>
70+ public EndPoint ServerHost
71+ {
72+ get { return _serverHost ; }
73+ }
74+
75+ /// <summary>
76+ /// 消息接收回调,当服务器发送消息时触发,
77+ /// 回调参数为服务器发送的消息对象
78+ /// 只有RPC没有正确处理时才会触发
79+ /// </summary>
80+ private readonly Action < MessageObject > _onMessageReceived ;
81+
82+ /// <summary>
83+ /// 每次重连之间的延迟时间,单位毫秒
84+ /// </summary>
85+ private readonly int _retryDelay ;
86+
87+ /// <summary>
88+ /// 最大重连次数,-1表示无限重试
89+ /// </summary>
90+ public int MaxRetryCount { get ; }
91+
92+ /// <summary>
93+ /// 标记当前实例是否已被释放,防止重复释放或空操作
94+ /// </summary>
95+ private bool _isDisposed ;
96+
97+ /// <summary>
98+ /// 初始化游戏服务TCP客户端
99+ /// </summary>
100+ /// <param name="endPoint">服务器端点信息(IP和端口)</param>
101+ /// <param name="onMessageReceived">消息接收回调,当服务器发送消息时触发</param>
102+ /// <param name="maxRetryCount">最大重连次数,-1表示无限重试</param>
103+ /// <param name="retryDelay">每次重连之间的延迟时间,单位毫秒</param>
104+ public GameAppServiceClient ( EndPoint endPoint , Action < MessageObject > onMessageReceived , int maxRetryCount = - 1 , int retryDelay = 1000 )
105+ {
106+ ArgumentNullException . ThrowIfNull ( endPoint , nameof ( endPoint ) ) ;
107+ _serverHost = endPoint ;
108+ _onMessageReceived = onMessageReceived ;
109+ _rpcSession = new RpcSession ( ) ;
110+ _retryDelay = retryDelay ;
111+ MaxRetryCount = maxRetryCount ;
112+ _mTcpClient = new AsyncTcpSession ( ) ;
113+ _mTcpClient . Connected += OnClientOnConnected ;
114+ _mTcpClient . Closed += OnClientOnClosed ;
115+ _mTcpClient . DataReceived += OnClientOnDataReceived ;
116+ _mTcpClient . Error += OnClientOnError ;
117+ Task . Run ( Handler ) ;
118+ Task . Run ( StartAsync ) ;
119+ }
120+
121+ private async Task Handler ( )
122+ {
123+ DateTime lastTickTime = DateTime . UtcNow ;
124+ while ( _isDisposed == false )
125+ {
126+ await Task . Delay ( 1 ) ;
127+ var deltaTime = DateTime . UtcNow - lastTickTime ;
128+ _rpcSession . Tick ( ( int ) deltaTime . TotalMilliseconds ) ;
129+
130+ while ( true )
131+ {
132+ var sessionData = _rpcSession . Handler ( ) ;
133+
134+ if ( sessionData != null )
135+ {
136+ MessageSendHandle ( sessionData ? . RequestMessage ) ;
137+ }
138+ else
139+ {
140+ break ;
141+ }
142+ }
143+
144+ lastTickTime = DateTime . UtcNow ;
145+ }
146+ }
147+
148+
149+ /// <summary>
150+ /// 启动客户端并尝试连接服务器,处理消息编解码和压缩解压缩处理器的初始化。
151+ /// 内部采用无限循环,在连接成功前持续重试,并在连接成功后周期性发送心跳。
152+ /// </summary>
153+ /// <returns>表示异步操作的任务。</returns>
154+ private async Task StartAsync ( )
155+ {
156+ // 主循环:负责连接、重连
157+ while ( true )
158+ {
159+ if ( _isDisposed )
160+ {
161+ break ;
162+ }
163+
164+ // 如果未连接且未处于连接中,则尝试连接
165+ if ( ! _mTcpClient . IsConnected && ! _mTcpClient . IsInConnecting )
166+ {
167+ LogHelper . Debug ( $ "try to connect to the target server...{ _serverHost } ") ;
168+ _mTcpClient . Connect ( _serverHost ) ;
169+ // 若连接成功或正在连接,则跳过本次循环
170+ if ( _mTcpClient . IsConnected || _mTcpClient . IsInConnecting )
171+ {
172+ continue ;
173+ }
174+
175+ // 未达到最大重连次数(或无限重试)则进行重连
176+ if ( RetryCount < MaxRetryCount || MaxRetryCount < 0 )
177+ {
178+ LogHelper . Info ( $ "Not connecting to the target server, attempts to reconnect (number of attempts: { RetryCount + 1 } /{ ( MaxRetryCount < 0 ? "∞" : MaxRetryCount . ToString ( ) ) } )...") ;
179+ _mTcpClient . Connect ( _serverHost ) ;
180+ RetryCount ++ ;
181+ await Task . Delay ( _retryDelay ) ;
182+ }
183+ else
184+ {
185+ LogHelper . Info ( $ "Reconnect attempts have reached the upper limit ({ MaxRetryCount } ), and no more attempts will be made.") ;
186+ break ;
187+ }
188+ }
189+ else
190+ {
191+ // 连接成功,重置重连计数
192+ RetryCount = 0 ;
193+ }
194+ }
195+ }
196+
197+ /// <summary>
198+ /// 发送消息到服务器
199+ /// 内部使用MessageHelper编码后通过TcpClient发送
200+ /// </summary>
201+ /// <param name="messageObject">要发送的消息对象</param>
202+ public void Send ( MessageObject messageObject )
203+ {
204+ _rpcSession . Send ( messageObject as IRequestMessage ) ;
205+ }
206+
207+ /// <summary>
208+ /// 发送消息到服务器
209+ /// 内部使用MessageHelper编码后通过TcpClient发送
210+ /// </summary>
211+ /// <param name="messageObject">要发送的消息对象</param>
212+ private void MessageSendHandle ( INetworkMessage messageObject )
213+ {
214+ var buffer = MessageHelper . EncoderHandler . Handler ( messageObject ) ;
215+ if ( buffer != null )
216+ {
217+ _mTcpClient . Send ( buffer ) ;
218+ }
219+ }
220+
221+ /// <summary>
222+ /// 发送消息到服务器
223+ /// 内部使用MessageHelper编码后通过TcpClient发送
224+ /// </summary>
225+ /// <param name="messageObject">要发送的消息对象</param>
226+ /// <param name="timeOut">超时时间,单位毫秒</param>
227+ /// <typeparam name="T">响应消息类型,必须实现IResponseMessage接口</typeparam>
228+ /// <returns>表示异步操作的任务,任务结果为IRpcResult对象</returns>
229+ public Task < IRpcResult > Call < T > ( MessageObject messageObject , int timeOut = 10000 ) where T : IResponseMessage , new ( )
230+ {
231+ var result = _rpcSession . Call < T > ( messageObject as IRequestMessage , timeOut ) ;
232+ return result ;
233+ }
234+
235+ /// <summary>
236+ /// 处理客户端错误事件
237+ /// 将错误信息通过GameAppClientEvent回调给上层
238+ /// </summary>
239+ /// <param name="client">触发事件的TcpSession对象</param>
240+ /// <param name="e">包含异常信息的错误事件参数</param>
241+ private void OnClientOnError ( object client , SuperSocket . ClientEngine . ErrorEventArgs e )
242+ {
243+ LogHelper . Error ( $ "Client error occurred: { e . Exception . Message } ") ;
244+ }
245+
246+ /// <summary>
247+ /// 处理客户端连接关闭事件
248+ /// 记录日志并通过GameAppClientEvent通知上层连接已断开
249+ /// </summary>
250+ /// <param name="client">触发事件的TcpSession对象</param>
251+ /// <param name="e">事件参数</param>
252+ private void OnClientOnClosed ( object client , EventArgs e )
253+ {
254+ LogHelper . Info ( $ "Client disconnected from the server: { _serverHost } ") ;
255+ }
256+
257+ /// <summary>
258+ /// 处理客户端连接成功事件
259+ /// 记录日志并通过GameAppClientEvent通知上层连接已建立
260+ /// </summary>
261+ /// <param name="client">触发事件的TcpSession对象</param>
262+ /// <param name="e">事件参数</param>
263+ private void OnClientOnConnected ( object client , EventArgs e )
264+ {
265+ LogHelper . Info ( $ "Client successfully connected to the server: { _serverHost } ") ;
266+ }
267+
268+ /// <summary>
269+ /// 处理接收到数据事件
270+ /// 将接收到的二进制数据解码为消息对象,若为内部网络消息则反序列化后通过回调通知上层
271+ /// </summary>
272+ /// <param name="client">触发事件的TcpSession对象</param>
273+ /// <param name="e">包含接收数据的数据事件参数</param>
274+ private void OnClientOnDataReceived ( object client , DataEventArgs e )
275+ {
276+ var message = MessageHelper . DecoderHandler . Handler ( e . Data . ReadBytesValue ( e . Offset , e . Length ) ) ;
277+ // 只处理内部消息
278+ if ( message is NetworkMessagePackage innerNetworkMessage && innerNetworkMessage . Header . MessageId < 0 )
279+ {
280+ var messageObject = ( MessageObject ) innerNetworkMessage . DeserializeMessageObject ( ) ;
281+ var reply = _rpcSession . Reply ( messageObject as IResponseMessage ) ;
282+ if ( ! reply )
283+ {
284+ _onMessageReceived ? . Invoke ( messageObject ) ;
285+ }
286+ }
287+ }
288+
289+ /// <summary>
290+ /// 停止客户端
291+ /// </summary>
292+ public void Dispose ( )
293+ {
294+ if ( _isDisposed )
295+ {
296+ return ;
297+ }
298+
299+ Stop ( ) ;
300+ }
301+
302+ /// <summary>
303+ /// 停止客户端连接,关闭底层TCP会话
304+ /// </summary>
305+ public async void Stop ( )
306+ {
307+ _isDisposed = true ;
308+ await Task . Delay ( 5 ) ;
309+ _mTcpClient . Close ( ) ;
310+ _rpcSession . Stop ( ) ;
311+ }
312+ }
0 commit comments