| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- using HslCommunication.Core;
- using HslCommunication.Core.Net;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- namespace HslCommunication.Enthernet
- {
- /// <summary>
- /// 高性能的异步网络服务器类,适合搭建局域网聊天程序,消息推送程序
- /// </summary>
- public class NetComplexServer : NetworkServerBase
- {
- #region 构造方法块
- /// <summary>
- /// 实例化一个网络服务器类对象
- /// </summary>
- public NetComplexServer( )
- {
- AsyncCoordinator = new HslAsyncCoordinator( new Action( CalculateOnlineClients ) );
- }
- #endregion
- #region 基本属性块
- private int m_Connect_Max = 1000;
- /// <summary>
- /// 所支持的同时在线客户端的最大数量,商用限制1000个,最小10个
- /// </summary>
- public int ConnectMax
- {
- get { return m_Connect_Max; }
- set
- {
- if (value >= 10 && value < 1001)
- {
- m_Connect_Max = value;
- }
- }
- }
- /// <summary>
- /// 客户端在线信息显示的格式化文本,如果自定义,必须#开头,
- /// 示例:"#IP:{0} Name:{1}"
- /// </summary>
- public string FormatClientOnline { get; set; } = "#IP:{0} Name:{1}";
- /// <summary>
- /// 客户端在线信息缓存
- /// </summary>
- private string m_AllClients = string.Empty;
- #region 高性能乐观并发模型的上下线控制
- private void CalculateOnlineClients( )
- {
- StringBuilder builder = new StringBuilder( );
- HybirdLockSockets.Enter( );
- for (int i = 0; i < All_sockets_connect.Count; i++)
- {
- builder.Append( string.Format( FormatClientOnline, All_sockets_connect[i].IpAddress
- , All_sockets_connect[i].LoginAlias ) );
- }
- HybirdLockSockets.Leave( );
- if (builder.Length > 0)
- {
- m_AllClients = builder.Remove( 0, 1 ).ToString( );
- }
- else
- {
- m_AllClients = string.Empty;
- }
- // 触发状态变更
- AllClientsStatusChange?.Invoke( m_AllClients );
- }
- /// <summary>
- /// 一个计算上线下线的高性能缓存对象
- /// </summary>
- private HslAsyncCoordinator AsyncCoordinator { get; set; }
- #endregion
- /// <summary>
- /// 计算所有客户端在线的信息
- /// </summary>
- /// <summary>
- /// 获取或设置服务器是否记录客户端上下线信息
- /// </summary>
- public bool IsSaveLogClientLineChange { get; set; } = true;
- /// <summary>
- /// 所有在线客户端的数量
- /// </summary>
- public int ClientCount => All_sockets_connect.Count;
- /// <summary>
- /// 所有的客户端连接的核心对象
- /// </summary>
- private List<AppSession> All_sockets_connect { get; set; } = new List<AppSession>( );
- /// <summary>
- /// 客户端数组操作的线程混合锁
- /// </summary>
- private SimpleHybirdLock HybirdLockSockets = new SimpleHybirdLock( );
- #endregion
- #region 启动停止块
- /// <summary>
- /// 初始化操作
- /// </summary>
- protected override void StartInitialization( )
- {
- Thread_heart_check = new Thread( new ThreadStart( ThreadHeartCheck ) )
- {
- IsBackground = true,
- Priority = ThreadPriority.AboveNormal
- };
- Thread_heart_check.Start( );
- base.StartInitialization( );
- }
- /// <summary>
- /// 关闭网络时的操作
- /// </summary>
- protected override void CloseAction( )
- {
- Thread_heart_check?.Abort( );
- ClientOffline = null;
- ClientOnline = null;
- AcceptString = null;
- AcceptByte = null;
- //关闭所有的网络
- All_sockets_connect.ForEach( m => m.WorkSocket?.Close( ) );
- base.CloseAction( );
- }
- #endregion
- #region 客户端上下线块
- private void TcpStateUpLine( AppSession state )
- {
- HybirdLockSockets.Enter( );
- All_sockets_connect.Add( state );
- HybirdLockSockets.Leave( );
- // 提示上线
- ClientOnline?.Invoke( state );
- // 是否保存上线信息
- if (IsSaveLogClientLineChange)
- {
- LogNet?.WriteInfo( ToString(), "IP:" + state.IpAddress + " Name:" + state?.LoginAlias + " " + StringResources.NetClientOnline );
- }
- // 计算客户端在线情况
- AsyncCoordinator.StartOperaterInfomation( );
- }
- private void TcpStateClose( AppSession state )
- {
- state?.WorkSocket.Close( );
- }
- private void TcpStateDownLine( AppSession state, bool is_regular )
- {
- HybirdLockSockets.Enter( );
- All_sockets_connect.Remove( state );
- HybirdLockSockets.Leave( );
- // 关闭连接
- TcpStateClose( state );
- // 判断是否正常下线
- string str = is_regular ? StringResources.NetClientOffline : StringResources.NetClientBreak;
- ClientOffline?.Invoke( state, str );
- // 是否保存上线信息
- if (IsSaveLogClientLineChange)
- {
- LogNet?.WriteInfo( ToString(), "IP:" + state.IpAddress + " Name:" + state?.LoginAlias + " " + str );
- }
- // 计算客户端在线情况
- AsyncCoordinator.StartOperaterInfomation( );
- }
- #endregion
- #region 事件委托块
- /// <summary>
- /// 客户端的上下限状态变更时触发,仅作为在线客户端识别
- /// </summary>
- public event Action<string> AllClientsStatusChange;
- /// <summary>
- /// 当客户端上线的时候,触发此事件
- /// </summary>
- public event Action<AppSession> ClientOnline;
- /// <summary>
- /// 当客户端下线的时候,触发此事件
- /// </summary>
- public event Action<AppSession, string> ClientOffline;
- /// <summary>
- /// 当接收到文本数据的时候,触发此事件
- /// </summary>
- public event Action<AppSession, NetHandle, string> AcceptString;
- /// <summary>
- /// 当接收到字节数据的时候,触发此事件
- /// </summary>
- public event Action<AppSession, NetHandle, byte[]> AcceptByte;
- #endregion
- #region 请求接入块
- /// <summary>
- /// 登录后的处理方法
- /// </summary>
- /// <param name="obj"></param>
- protected override void ThreadPoolLogin( object obj )
- {
- if (obj is Socket)
- {
- Socket socket = obj as Socket;
- // 判断连接数是否超出规定
- if (All_sockets_connect.Count > ConnectMax)
- {
- socket?.Close( );
- LogNet?.WriteWarn( ToString(), StringResources.NetClientFull );
- return;
- }
- // 接收用户别名并验证令牌
- OperateResult result = new OperateResult( );
- OperateResult<int, string> readResult = ReceiveStringContentFromSocket( socket );
- if (!readResult.IsSuccess)
- {
- socket?.Close( );
- return;
- }
- // 登录成功
- AppSession session = new AppSession( )
- {
- WorkSocket = socket,
- LoginAlias = readResult.Content2,
- };
- try
- {
- session.IpEndPoint = (IPEndPoint)socket.RemoteEndPoint;
- session.IpAddress = ((IPEndPoint)socket.RemoteEndPoint).Address.ToString( );
- }
- catch(Exception ex)
- {
- LogNet?.WriteException( ToString( ), "客户端地址获取失败:", ex );
- }
- if (readResult.Content1 == 1)
- {
- // 电脑端客户端
- session.ClientType = "Windows";
- }
- else if (readResult.Content1 == 2)
- {
- // Android 客户端
- session.ClientType = "Android";
- }
- try
- {
- session.WorkSocket.BeginReceive( session.BytesHead, session.AlreadyReceivedHead,
- session.BytesHead.Length - session.AlreadyReceivedHead, SocketFlags.None,
- new AsyncCallback( HeadBytesReceiveCallback ), session );
- TcpStateUpLine( session );
- Thread.Sleep( 500 );//留下一些时间进行反应
- }
- catch (Exception ex)
- {
- //登录前已经出错
- TcpStateClose( session );
- LogNet?.WriteException( ToString(), StringResources.NetClientLoginFailed, ex );
- }
- }
- }
- #endregion
- #region 异步接收发送块
- /// <summary>
- /// 异常下线
- /// </summary>
- /// <param name="session"></param>
- /// <param name="ex"></param>
- internal override void SocketReceiveException( AppSession session, Exception ex )
- {
- if (ex.Message.Contains( StringResources.SocketRemoteCloseException ))
- {
- //异常掉线
- TcpStateDownLine( session, false );
- }
- }
- /// <summary>
- /// 正常下线
- /// </summary>
- /// <param name="session"></param>
- internal override void AppSessionRemoteClose( AppSession session )
- {
- TcpStateDownLine( session, true );
- }
- /// <summary>
- /// 服务器端用于数据发送文本的方法
- /// </summary>
- /// <param name="session">数据发送对象</param>
- /// <param name="customer">用户自定义的数据对象,如不需要,赋值为0</param>
- /// <param name="str">发送的文本</param>
- public void Send( AppSession session, NetHandle customer, string str )
- {
- SendBytes( session, HslProtocol.CommandBytes( customer, Token, str ) );
- }
- /// <summary>
- /// 服务器端用于发送字节的方法
- /// </summary>
- /// <param name="session">数据发送对象</param>
- /// <param name="customer">用户自定义的数据对象,如不需要,赋值为0</param>
- /// <param name="bytes">实际发送的数据</param>
- public void Send( AppSession session, NetHandle customer, byte[] bytes )
- {
- SendBytes( session, HslProtocol.CommandBytes( customer, Token, bytes ) );
- }
- private void SendBytes( AppSession session, byte[] content )
- {
- SendBytesAsync( session, content );
- }
- /// <summary>
- /// 服务端用于发送所有数据到所有的客户端
- /// </summary>
- /// <param name="customer">用户自定义的命令头</param>
- /// <param name="str">需要传送的实际的数据</param>
- public void SendAllClients( NetHandle customer, string str )
- {
- for (int i = 0; i < All_sockets_connect.Count; i++)
- {
- Send( All_sockets_connect[i], customer, str );
- }
- }
- /// <summary>
- /// 服务端用于发送所有数据到所有的客户端
- /// </summary>
- /// <param name="customer">用户自定义的命令头</param>
- /// <param name="data">需要群发客户端的字节数据</param>
- public void SendAllClients( NetHandle customer, byte[] data )
- {
- for (int i = 0; i < All_sockets_connect.Count; i++)
- {
- Send( All_sockets_connect[i], customer, data );
- }
- }
- /// <summary>
- /// 根据客户端设置的别名进行发送消息
- /// </summary>
- /// <param name="Alias">客户端上线的别名</param>
- /// <param name="customer">用户自定义的命令头</param>
- /// <param name="str">需要传送的实际的数据</param>
- public void SendClientByAlias( string Alias, NetHandle customer, string str )
- {
- for (int i = 0; i < All_sockets_connect.Count; i++)
- {
- if (All_sockets_connect[i].LoginAlias == Alias)
- {
- Send( All_sockets_connect[i], customer, str );
- }
- }
- }
- /// <summary>
- /// 根据客户端设置的别名进行发送消息
- /// </summary>
- /// <param name="Alias">客户端上线的别名</param>
- /// <param name="customer">用户自定义的命令头</param>
- /// <param name="data">需要传送的实际的数据</param>
- public void SendClientByAlias( string Alias, NetHandle customer, byte[] data )
- {
- for (int i = 0; i < All_sockets_connect.Count; i++)
- {
- if (All_sockets_connect[i].LoginAlias == Alias)
- {
- Send( All_sockets_connect[i], customer, data );
- }
- }
- }
- #endregion
- #region 数据中心处理块
- /// <summary>
- /// 数据处理中心
- /// </summary>
- /// <param name="session"></param>
- /// <param name="protocol"></param>
- /// <param name="customer"></param>
- /// <param name="content"></param>
- internal override void DataProcessingCenter( AppSession session, int protocol, int customer, byte[] content )
- {
- if (protocol == HslProtocol.ProtocolCheckSecends)
- {
- BitConverter.GetBytes( DateTime.Now.Ticks ).CopyTo( content, 8 );
- SendBytes( session, HslProtocol.CommandBytes( HslProtocol.ProtocolCheckSecends, customer, Token, content ) );
- session.HeartTime = DateTime.Now;
- }
- else if (protocol == HslProtocol.ProtocolClientQuit)
- {
- TcpStateDownLine( session, true );
- }
- else if (protocol == HslProtocol.ProtocolUserBytes)
- {
- //接收到字节数据
- AcceptByte?.Invoke( session, customer, content );
- }
- else if (protocol == HslProtocol.ProtocolUserString)
- {
- //接收到文本数据
- string str = Encoding.Unicode.GetString( content );
- AcceptString?.Invoke( session, customer, str );
- }
- else
- {
- // 其他一概不处理
- }
- }
- #endregion
- #region 心跳线程块
- private Thread Thread_heart_check { get; set; } = null;
- private void ThreadHeartCheck( )
- {
- while (true)
- {
- Thread.Sleep( 2000 );
- try
- {
- for (int i = All_sockets_connect.Count - 1; i >= 0; i--)
- {
- if (All_sockets_connect[i] == null)
- {
- All_sockets_connect.RemoveAt( i );
- continue;
- }
- if ((DateTime.Now - All_sockets_connect[i].HeartTime).TotalSeconds > 1 * 8)//8次没有收到失去联系
- {
- LogNet?.WriteWarn( ToString(), "心跳验证超时,强制下线:" + All_sockets_connect[i].IpAddress.ToString( ) );
- TcpStateDownLine( All_sockets_connect[i], false );
- continue;
- }
- }
- }
- catch (Exception ex)
- {
- LogNet?.WriteException( ToString(), "心跳线程异常:", ex );
- }
- if (!IsStarted) break;
- }
- }
- #endregion
- #region Object Override
- /// <summary>
- /// 获取本对象的字符串表示形式
- /// </summary>
- /// <returns></returns>
- public override string ToString( )
- {
- return "NetComplexServer";
- }
- #endregion
- }
- }
|