NetComplexServer.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. using HslCommunication.Core;
  2. using HslCommunication.Core.Net;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Text;
  9. using System.Threading;
  10. namespace HslCommunication.Enthernet
  11. {
  12. /// <summary>
  13. /// 高性能的异步网络服务器类,适合搭建局域网聊天程序,消息推送程序
  14. /// </summary>
  15. public class NetComplexServer : NetworkServerBase
  16. {
  17. #region 构造方法块
  18. /// <summary>
  19. /// 实例化一个网络服务器类对象
  20. /// </summary>
  21. public NetComplexServer( )
  22. {
  23. AsyncCoordinator = new HslAsyncCoordinator( new Action( CalculateOnlineClients ) );
  24. }
  25. #endregion
  26. #region 基本属性块
  27. private int m_Connect_Max = 1000;
  28. /// <summary>
  29. /// 所支持的同时在线客户端的最大数量,商用限制1000个,最小10个
  30. /// </summary>
  31. public int ConnectMax
  32. {
  33. get { return m_Connect_Max; }
  34. set
  35. {
  36. if (value >= 10 && value < 1001)
  37. {
  38. m_Connect_Max = value;
  39. }
  40. }
  41. }
  42. /// <summary>
  43. /// 客户端在线信息显示的格式化文本,如果自定义,必须#开头,
  44. /// 示例:"#IP:{0} Name:{1}"
  45. /// </summary>
  46. public string FormatClientOnline { get; set; } = "#IP:{0} Name:{1}";
  47. /// <summary>
  48. /// 客户端在线信息缓存
  49. /// </summary>
  50. private string m_AllClients = string.Empty;
  51. #region 高性能乐观并发模型的上下线控制
  52. private void CalculateOnlineClients( )
  53. {
  54. StringBuilder builder = new StringBuilder( );
  55. HybirdLockSockets.Enter( );
  56. for (int i = 0; i < All_sockets_connect.Count; i++)
  57. {
  58. builder.Append( string.Format( FormatClientOnline, All_sockets_connect[i].IpAddress
  59. , All_sockets_connect[i].LoginAlias ) );
  60. }
  61. HybirdLockSockets.Leave( );
  62. if (builder.Length > 0)
  63. {
  64. m_AllClients = builder.Remove( 0, 1 ).ToString( );
  65. }
  66. else
  67. {
  68. m_AllClients = string.Empty;
  69. }
  70. // 触发状态变更
  71. AllClientsStatusChange?.Invoke( m_AllClients );
  72. }
  73. /// <summary>
  74. /// 一个计算上线下线的高性能缓存对象
  75. /// </summary>
  76. private HslAsyncCoordinator AsyncCoordinator { get; set; }
  77. #endregion
  78. /// <summary>
  79. /// 计算所有客户端在线的信息
  80. /// </summary>
  81. /// <summary>
  82. /// 获取或设置服务器是否记录客户端上下线信息
  83. /// </summary>
  84. public bool IsSaveLogClientLineChange { get; set; } = true;
  85. /// <summary>
  86. /// 所有在线客户端的数量
  87. /// </summary>
  88. public int ClientCount => All_sockets_connect.Count;
  89. /// <summary>
  90. /// 所有的客户端连接的核心对象
  91. /// </summary>
  92. private List<AppSession> All_sockets_connect { get; set; } = new List<AppSession>( );
  93. /// <summary>
  94. /// 客户端数组操作的线程混合锁
  95. /// </summary>
  96. private SimpleHybirdLock HybirdLockSockets = new SimpleHybirdLock( );
  97. #endregion
  98. #region 启动停止块
  99. /// <summary>
  100. /// 初始化操作
  101. /// </summary>
  102. protected override void StartInitialization( )
  103. {
  104. Thread_heart_check = new Thread( new ThreadStart( ThreadHeartCheck ) )
  105. {
  106. IsBackground = true,
  107. Priority = ThreadPriority.AboveNormal
  108. };
  109. Thread_heart_check.Start( );
  110. base.StartInitialization( );
  111. }
  112. /// <summary>
  113. /// 关闭网络时的操作
  114. /// </summary>
  115. protected override void CloseAction( )
  116. {
  117. Thread_heart_check?.Abort( );
  118. ClientOffline = null;
  119. ClientOnline = null;
  120. AcceptString = null;
  121. AcceptByte = null;
  122. //关闭所有的网络
  123. All_sockets_connect.ForEach( m => m.WorkSocket?.Close( ) );
  124. base.CloseAction( );
  125. }
  126. #endregion
  127. #region 客户端上下线块
  128. private void TcpStateUpLine( AppSession state )
  129. {
  130. HybirdLockSockets.Enter( );
  131. All_sockets_connect.Add( state );
  132. HybirdLockSockets.Leave( );
  133. // 提示上线
  134. ClientOnline?.Invoke( state );
  135. // 是否保存上线信息
  136. if (IsSaveLogClientLineChange)
  137. {
  138. LogNet?.WriteInfo( ToString(), "IP:" + state.IpAddress + " Name:" + state?.LoginAlias + " " + StringResources.NetClientOnline );
  139. }
  140. // 计算客户端在线情况
  141. AsyncCoordinator.StartOperaterInfomation( );
  142. }
  143. private void TcpStateClose( AppSession state )
  144. {
  145. state?.WorkSocket.Close( );
  146. }
  147. private void TcpStateDownLine( AppSession state, bool is_regular )
  148. {
  149. HybirdLockSockets.Enter( );
  150. All_sockets_connect.Remove( state );
  151. HybirdLockSockets.Leave( );
  152. // 关闭连接
  153. TcpStateClose( state );
  154. // 判断是否正常下线
  155. string str = is_regular ? StringResources.NetClientOffline : StringResources.NetClientBreak;
  156. ClientOffline?.Invoke( state, str );
  157. // 是否保存上线信息
  158. if (IsSaveLogClientLineChange)
  159. {
  160. LogNet?.WriteInfo( ToString(), "IP:" + state.IpAddress + " Name:" + state?.LoginAlias + " " + str );
  161. }
  162. // 计算客户端在线情况
  163. AsyncCoordinator.StartOperaterInfomation( );
  164. }
  165. #endregion
  166. #region 事件委托块
  167. /// <summary>
  168. /// 客户端的上下限状态变更时触发,仅作为在线客户端识别
  169. /// </summary>
  170. public event Action<string> AllClientsStatusChange;
  171. /// <summary>
  172. /// 当客户端上线的时候,触发此事件
  173. /// </summary>
  174. public event Action<AppSession> ClientOnline;
  175. /// <summary>
  176. /// 当客户端下线的时候,触发此事件
  177. /// </summary>
  178. public event Action<AppSession, string> ClientOffline;
  179. /// <summary>
  180. /// 当接收到文本数据的时候,触发此事件
  181. /// </summary>
  182. public event Action<AppSession, NetHandle, string> AcceptString;
  183. /// <summary>
  184. /// 当接收到字节数据的时候,触发此事件
  185. /// </summary>
  186. public event Action<AppSession, NetHandle, byte[]> AcceptByte;
  187. #endregion
  188. #region 请求接入块
  189. /// <summary>
  190. /// 登录后的处理方法
  191. /// </summary>
  192. /// <param name="obj"></param>
  193. protected override void ThreadPoolLogin( object obj )
  194. {
  195. if (obj is Socket)
  196. {
  197. Socket socket = obj as Socket;
  198. // 判断连接数是否超出规定
  199. if (All_sockets_connect.Count > ConnectMax)
  200. {
  201. socket?.Close( );
  202. LogNet?.WriteWarn( ToString(), StringResources.NetClientFull );
  203. return;
  204. }
  205. // 接收用户别名并验证令牌
  206. OperateResult result = new OperateResult( );
  207. OperateResult<int, string> readResult = ReceiveStringContentFromSocket( socket );
  208. if (!readResult.IsSuccess)
  209. {
  210. socket?.Close( );
  211. return;
  212. }
  213. // 登录成功
  214. AppSession session = new AppSession( )
  215. {
  216. WorkSocket = socket,
  217. LoginAlias = readResult.Content2,
  218. };
  219. try
  220. {
  221. session.IpEndPoint = (IPEndPoint)socket.RemoteEndPoint;
  222. session.IpAddress = ((IPEndPoint)socket.RemoteEndPoint).Address.ToString( );
  223. }
  224. catch(Exception ex)
  225. {
  226. LogNet?.WriteException( ToString( ), "客户端地址获取失败:", ex );
  227. }
  228. if (readResult.Content1 == 1)
  229. {
  230. // 电脑端客户端
  231. session.ClientType = "Windows";
  232. }
  233. else if (readResult.Content1 == 2)
  234. {
  235. // Android 客户端
  236. session.ClientType = "Android";
  237. }
  238. try
  239. {
  240. session.WorkSocket.BeginReceive( session.BytesHead, session.AlreadyReceivedHead,
  241. session.BytesHead.Length - session.AlreadyReceivedHead, SocketFlags.None,
  242. new AsyncCallback( HeadBytesReceiveCallback ), session );
  243. TcpStateUpLine( session );
  244. Thread.Sleep( 500 );//留下一些时间进行反应
  245. }
  246. catch (Exception ex)
  247. {
  248. //登录前已经出错
  249. TcpStateClose( session );
  250. LogNet?.WriteException( ToString(), StringResources.NetClientLoginFailed, ex );
  251. }
  252. }
  253. }
  254. #endregion
  255. #region 异步接收发送块
  256. /// <summary>
  257. /// 异常下线
  258. /// </summary>
  259. /// <param name="session"></param>
  260. /// <param name="ex"></param>
  261. internal override void SocketReceiveException( AppSession session, Exception ex )
  262. {
  263. if (ex.Message.Contains( StringResources.SocketRemoteCloseException ))
  264. {
  265. //异常掉线
  266. TcpStateDownLine( session, false );
  267. }
  268. }
  269. /// <summary>
  270. /// 正常下线
  271. /// </summary>
  272. /// <param name="session"></param>
  273. internal override void AppSessionRemoteClose( AppSession session )
  274. {
  275. TcpStateDownLine( session, true );
  276. }
  277. /// <summary>
  278. /// 服务器端用于数据发送文本的方法
  279. /// </summary>
  280. /// <param name="session">数据发送对象</param>
  281. /// <param name="customer">用户自定义的数据对象,如不需要,赋值为0</param>
  282. /// <param name="str">发送的文本</param>
  283. public void Send( AppSession session, NetHandle customer, string str )
  284. {
  285. SendBytes( session, HslProtocol.CommandBytes( customer, Token, str ) );
  286. }
  287. /// <summary>
  288. /// 服务器端用于发送字节的方法
  289. /// </summary>
  290. /// <param name="session">数据发送对象</param>
  291. /// <param name="customer">用户自定义的数据对象,如不需要,赋值为0</param>
  292. /// <param name="bytes">实际发送的数据</param>
  293. public void Send( AppSession session, NetHandle customer, byte[] bytes )
  294. {
  295. SendBytes( session, HslProtocol.CommandBytes( customer, Token, bytes ) );
  296. }
  297. private void SendBytes( AppSession session, byte[] content )
  298. {
  299. SendBytesAsync( session, content );
  300. }
  301. /// <summary>
  302. /// 服务端用于发送所有数据到所有的客户端
  303. /// </summary>
  304. /// <param name="customer">用户自定义的命令头</param>
  305. /// <param name="str">需要传送的实际的数据</param>
  306. public void SendAllClients( NetHandle customer, string str )
  307. {
  308. for (int i = 0; i < All_sockets_connect.Count; i++)
  309. {
  310. Send( All_sockets_connect[i], customer, str );
  311. }
  312. }
  313. /// <summary>
  314. /// 服务端用于发送所有数据到所有的客户端
  315. /// </summary>
  316. /// <param name="customer">用户自定义的命令头</param>
  317. /// <param name="data">需要群发客户端的字节数据</param>
  318. public void SendAllClients( NetHandle customer, byte[] data )
  319. {
  320. for (int i = 0; i < All_sockets_connect.Count; i++)
  321. {
  322. Send( All_sockets_connect[i], customer, data );
  323. }
  324. }
  325. /// <summary>
  326. /// 根据客户端设置的别名进行发送消息
  327. /// </summary>
  328. /// <param name="Alias">客户端上线的别名</param>
  329. /// <param name="customer">用户自定义的命令头</param>
  330. /// <param name="str">需要传送的实际的数据</param>
  331. public void SendClientByAlias( string Alias, NetHandle customer, string str )
  332. {
  333. for (int i = 0; i < All_sockets_connect.Count; i++)
  334. {
  335. if (All_sockets_connect[i].LoginAlias == Alias)
  336. {
  337. Send( All_sockets_connect[i], customer, str );
  338. }
  339. }
  340. }
  341. /// <summary>
  342. /// 根据客户端设置的别名进行发送消息
  343. /// </summary>
  344. /// <param name="Alias">客户端上线的别名</param>
  345. /// <param name="customer">用户自定义的命令头</param>
  346. /// <param name="data">需要传送的实际的数据</param>
  347. public void SendClientByAlias( string Alias, NetHandle customer, byte[] data )
  348. {
  349. for (int i = 0; i < All_sockets_connect.Count; i++)
  350. {
  351. if (All_sockets_connect[i].LoginAlias == Alias)
  352. {
  353. Send( All_sockets_connect[i], customer, data );
  354. }
  355. }
  356. }
  357. #endregion
  358. #region 数据中心处理块
  359. /// <summary>
  360. /// 数据处理中心
  361. /// </summary>
  362. /// <param name="session"></param>
  363. /// <param name="protocol"></param>
  364. /// <param name="customer"></param>
  365. /// <param name="content"></param>
  366. internal override void DataProcessingCenter( AppSession session, int protocol, int customer, byte[] content )
  367. {
  368. if (protocol == HslProtocol.ProtocolCheckSecends)
  369. {
  370. BitConverter.GetBytes( DateTime.Now.Ticks ).CopyTo( content, 8 );
  371. SendBytes( session, HslProtocol.CommandBytes( HslProtocol.ProtocolCheckSecends, customer, Token, content ) );
  372. session.HeartTime = DateTime.Now;
  373. }
  374. else if (protocol == HslProtocol.ProtocolClientQuit)
  375. {
  376. TcpStateDownLine( session, true );
  377. }
  378. else if (protocol == HslProtocol.ProtocolUserBytes)
  379. {
  380. //接收到字节数据
  381. AcceptByte?.Invoke( session, customer, content );
  382. }
  383. else if (protocol == HslProtocol.ProtocolUserString)
  384. {
  385. //接收到文本数据
  386. string str = Encoding.Unicode.GetString( content );
  387. AcceptString?.Invoke( session, customer, str );
  388. }
  389. else
  390. {
  391. // 其他一概不处理
  392. }
  393. }
  394. #endregion
  395. #region 心跳线程块
  396. private Thread Thread_heart_check { get; set; } = null;
  397. private void ThreadHeartCheck( )
  398. {
  399. while (true)
  400. {
  401. Thread.Sleep( 2000 );
  402. try
  403. {
  404. for (int i = All_sockets_connect.Count - 1; i >= 0; i--)
  405. {
  406. if (All_sockets_connect[i] == null)
  407. {
  408. All_sockets_connect.RemoveAt( i );
  409. continue;
  410. }
  411. if ((DateTime.Now - All_sockets_connect[i].HeartTime).TotalSeconds > 1 * 8)//8次没有收到失去联系
  412. {
  413. LogNet?.WriteWarn( ToString(), "心跳验证超时,强制下线:" + All_sockets_connect[i].IpAddress.ToString( ) );
  414. TcpStateDownLine( All_sockets_connect[i], false );
  415. continue;
  416. }
  417. }
  418. }
  419. catch (Exception ex)
  420. {
  421. LogNet?.WriteException( ToString(), "心跳线程异常:", ex );
  422. }
  423. if (!IsStarted) break;
  424. }
  425. }
  426. #endregion
  427. #region Object Override
  428. /// <summary>
  429. /// 获取本对象的字符串表示形式
  430. /// </summary>
  431. /// <returns></returns>
  432. public override string ToString( )
  433. {
  434. return "NetComplexServer";
  435. }
  436. #endregion
  437. }
  438. }