using HslCommunication.Core.Net; using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; namespace HslCommunication.Enthernet { /// <summary> /// 一个基于异步高性能的客户端网络类,支持主动接收服务器的消息 /// </summary> /// <remarks> /// 详细的使用说明,请参照博客<a href="http://www.cnblogs.com/dathlin/p/7697782.html">http://www.cnblogs.com/dathlin/p/7697782.html</a> /// </remarks> /// <example> /// 此处贴上了Demo项目的服务器配置的示例代码 /// <code lang="cs" source="TestProject\HslCommunicationDemo\FormComplexNet.cs" region="NetComplexClient" title="NetComplexClient示例" /> /// </example> public class NetComplexClient : NetworkXBase { #region Constructor /// <summary> /// 实例化一个对象 /// </summary> public NetComplexClient() { session = new AppSession(); ServerTime = DateTime.Now; EndPointServer = new IPEndPoint(IPAddress.Any, 0); } #endregion #region Private Member private AppSession session; // 客户端的核心连接对象 private int isConnecting = 0; // 指示客户端是否处于连接服务器中,0代表未连接,1代表连接中 private bool IsQuie = false; // 指示系统是否准备退出 private Thread thread_heart_check = null; // 心跳线程 #endregion #region Public Properties /// <summary> /// 客户端系统是否启动 /// </summary> public bool IsClientStart { get; set; } /// <summary> /// 重连接失败的次数 /// </summary> public int ConnectFailedCount { get; private set; } /// <summary> /// 客户端登录的标识名称,可以为ID号,也可以为登录名 /// </summary> public string ClientAlias { get; set; } = string.Empty; /// <summary> /// 远程服务器的IP地址和端口 /// </summary> public IPEndPoint EndPointServer { get; set; } /// <summary> /// 服务器的时间,自动实现和服务器同步 /// </summary> public DateTime ServerTime { get; private set; } /// <summary> /// 系统与服务器的延时时间,单位毫秒 /// </summary> public int DelayTime { get; private set; } #endregion #region Event Handle /// <summary> /// 客户端启动成功的事件,重连成功也将触发此事件 /// </summary> public event Action LoginSuccess; /// <summary> /// 连接失败时触发的事件 /// </summary> public event Action<int> LoginFailed; /// <summary> /// 服务器的异常,启动,等等一般消息产生的时候,出发此事件 /// </summary> public event Action<string> MessageAlerts; /// <summary> /// 在客户端断开后并在重连服务器之前触发,用于清理系统资源 /// </summary> public event Action BeforReConnected; /// <summary> /// 当接收到文本数据的时候,触发此事件 /// </summary> public event Action<AppSession, NetHandle, string> AcceptString; /// <summary> /// 当接收到字节数据的时候,触发此事件 /// </summary> public event Action<AppSession, NetHandle, byte[]> AcceptByte; #endregion #region Start Close Support /// <summary> /// 关闭该客户端引擎 /// </summary> public void ClientClose() { IsQuie = true; if (IsClientStart) SendBytes(session, HslProtocol.CommandBytes(HslProtocol.ProtocolClientQuit, 0, Token, null)); IsClientStart = false; // 关闭客户端 thread_heart_check = null; LoginSuccess = null; // 清空所有的事件 LoginFailed = null; MessageAlerts = null; AcceptByte = null; AcceptString = null; try { session.WorkSocket?.Shutdown(SocketShutdown.Both); session.WorkSocket?.Close(); } catch { } LogNet?.WriteDebug(ToString(), "Client Close."); } /// <summary> /// 启动客户端引擎,连接服务器系统 /// </summary> public void ClientStart() { // 如果处于连接中就退出 if (Interlocked.CompareExchange(ref isConnecting, 1, 0) != 0) return; // 启动后台线程连接 new Thread(new ThreadStart(ThreadLogin)) { IsBackground = true }.Start(); // 启动心跳线程,在第一次Start的时候 if (thread_heart_check == null) { thread_heart_check = new Thread(new ThreadStart(ThreadHeartCheck)) { Priority = ThreadPriority.AboveNormal, IsBackground = true }; thread_heart_check.Start(); } } /// <summary> /// 连接服务器之前的消息提示,如果是重连的话,就提示10秒等待信息 /// </summary> private void AwaitToConnect() { if (ConnectFailedCount == 0) { MessageAlerts?.Invoke(StringResources.Language.ConnectingServer); } else { int count = 10; while (count > 0) { if (IsQuie) return; count--; MessageAlerts?.Invoke(string.Format(StringResources.Language.ConnectFailedAndWait, count)); Thread.Sleep(1000); } MessageAlerts?.Invoke(string.Format(StringResources.Language.AttemptConnectServer, ConnectFailedCount)); } } private void ConnectFailed() { ConnectFailedCount++; Interlocked.Exchange(ref isConnecting, 0); LoginFailed?.Invoke(ConnectFailedCount); LogNet?.WriteDebug(ToString(), "Connected Failed, Times: " + ConnectFailedCount); } private OperateResult<Socket> ConnectServer() { OperateResult<Socket> connectResult = CreateSocketAndConnect(EndPointServer, 10000); if (!connectResult.IsSuccess) { return connectResult; } // 连接成功,发送数据信息 OperateResult sendResult = SendStringAndCheckReceive(connectResult.Content, 1, ClientAlias); if (!sendResult.IsSuccess) { return OperateResult.CreateFailedResult<Socket>(sendResult); } MessageAlerts?.Invoke(StringResources.Language.ConnectServerSuccess); return connectResult; } private void LoginSuccessMethod(Socket socket) { ConnectFailedCount = 0; try { session.IpEndPoint = (IPEndPoint)socket.RemoteEndPoint; session.LoginAlias = ClientAlias; session.WorkSocket = socket; session.HeartTime = DateTime.Now; IsClientStart = true; ReBeginReceiveHead(session, false); } catch (Exception ex) { LogNet?.WriteException(ToString(), ex); } } private void ThreadLogin() { // 连接的消息等待 AwaitToConnect(); OperateResult<Socket> connectResult = ConnectServer(); if (!connectResult.IsSuccess) { ConnectFailed(); // 连接失败,重新连接服务器 ThreadPool.QueueUserWorkItem(new WaitCallback(ReconnectServer), null); return; } // 登录成功 LoginSuccessMethod(connectResult.Content); // 登录成功 LoginSuccess?.Invoke(); Interlocked.Exchange(ref isConnecting, 0); Thread.Sleep(200); } private void ReconnectServer(object obj = null) { // 是否连接服务器中,已经在连接的话,则不再连接 if (isConnecting == 1) return; // 是否退出了系统,退出则不再重连 if (IsQuie) return; // 触发连接失败,重连系统前错误 BeforReConnected?.Invoke(); session?.WorkSocket?.Close(); // 重新启动客户端 ClientStart(); } #endregion #region Send Message Support /// <summary> /// 通信出错后的处理 /// </summary> /// <param name="receive">接收的会话</param> /// <param name="ex">异常</param> internal override void SocketReceiveException(AppSession receive, Exception ex) { if (ex.Message.Contains(StringResources.Language.SocketRemoteCloseException)) { // 异常掉线 ReconnectServer(); } else { // MessageAlerts?.Invoke("数据接收出错:" + ex.Message); } LogNet?.WriteDebug(ToString(), "Socket Excepiton Occured."); } /// <summary> /// 服务器端用于数据发送文本的方法 /// </summary> /// <param name="customer">用户自定义的命令头</param> /// <param name="str">发送的文本</param> public void Send(NetHandle customer, string str) { if (IsClientStart) { SendBytes(session, HslProtocol.CommandBytes(customer, Token, str)); } } /// <summary> /// 服务器端用于发送字节的方法 /// </summary> /// <param name="customer">用户自定义的命令头</param> /// <param name="bytes">实际发送的数据</param> public void Send(NetHandle customer, byte[] bytes) { if (IsClientStart) { SendBytes(session, HslProtocol.CommandBytes(customer, Token, bytes)); } } private void SendBytes(AppSession stateone, byte[] content) { SendBytesAsync(stateone, content); } #endregion #region Data Process Center /// <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) { DateTime dt = new DateTime(BitConverter.ToInt64(content, 0)); ServerTime = new DateTime(BitConverter.ToInt64(content, 8)); DelayTime = (int)(DateTime.Now - dt).TotalMilliseconds; this.session.HeartTime = DateTime.Now; // MessageAlerts?.Invoke("心跳时间:" + DateTime.Now.ToString()); } else if (protocol == HslProtocol.ProtocolClientQuit) { // 申请了退出 } else if (protocol == HslProtocol.ProtocolUserBytes) { // 接收到字节数据 AcceptByte?.Invoke(this.session, customer, content); } else if (protocol == HslProtocol.ProtocolUserString) { // 接收到文本数据 string str = Encoding.Unicode.GetString(content); AcceptString?.Invoke(this.session, customer, str); } } #endregion #region Heart Check /// <summary> /// 心跳线程的方法 /// </summary> private void ThreadHeartCheck() { Thread.Sleep(2000); while (true) { Thread.Sleep(1000); if (!IsQuie) { byte[] send = new byte[16]; BitConverter.GetBytes(DateTime.Now.Ticks).CopyTo(send, 0); SendBytes(session, HslProtocol.CommandBytes(HslProtocol.ProtocolCheckSecends, 0, Token, send)); double timeSpan = (DateTime.Now - session.HeartTime).TotalSeconds; if (timeSpan > 1 * 8)//8次没有收到失去联系 { if (isConnecting == 0) { LogNet?.WriteDebug(ToString(), $"Heart Check Failed int {timeSpan} Seconds."); ReconnectServer(); } if (!IsQuie) Thread.Sleep(1000); } } else { break; } } } #endregion #region Object Override /// <summary> /// 返回对象的字符串表示形式 /// </summary> /// <returns></returns> public override string ToString() { return "NetComplexClient"; } #endregion } }