zoukankan      html  css  js  c++  java
  • 切实解决socket连接掉线检测

     版权声明:欢迎转载,但是请保留出处说明 https://blog.csdn.net/lanwilliam/article/details/51698807

    新公司在做物联网,要做与modbus设备的通讯服务。在过程中除了研究modbus协议外,最麻烦的就是设备在线状态的检测问题。

    Socket本身无法很好的捕获连接断开事件,或者说根本没这功能。总不能每次发生数据通讯时,通过异常来判断吧。

    所以经过了各种测试及查询(这里还是要感谢国外的友人们,鄙视一下国人),总算找到一种相对稳定的方法。

    该方法利用了tcp/ip协议本省的keep-alive规则。

    keep-alive简单来说,就是tcp协议中制定的心跳检测,用来判断连接是否存活。默认是不启动的,需要进行设置。

    serverFullAddr = new IPEndPoint(IPAddress.Any, portNo);//设置IP,端口
                server = new TcpListener(serverFullAddr);
                server.Start();
                // 启用keep-alive
                server.Server.IOControl(IOControlCode.KeepAliveValues, GetKeepAliveData(), null);
                server.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

    上面是server端演示代码,这里我用的是tcplistener,毕竟比较方便。而且用来和DTU通讯的时候,使用的NetworkStream,这个相对好用。
    client = server.AcceptTcpClient();
    
                        // 启用keep-alive
                        client.Client.IOControl(IOControlCode.KeepAliveValues, GetKeepAliveData(), null);
                        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

    上面是客户端启用的设置。

    按照查询到的理论,应该任意一方面设置了就可以,不过我这里都启用了,姑且算保险吧。

    然后就是IOControl设置的数据了。

    private byte[] GetKeepAliveData()
            {
                uint dummy = 0;
                byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
                BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
                BitConverter.GetBytes((uint)3000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//keep-alive间隔
                BitConverter.GetBytes((uint)500).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);// 尝试间隔
                return inOptionValues;
            }
    keep-alive如果使用windows默认,可能2个小时才会发送一次心跳,我这里检测设备在线,肯定不能这么长时间,我的读数频率都是以分钟作为单位的,出于各方面考虑

    我这里设置的keep-alive每3秒发送一次。如果对方没有响应,每0.5秒后发送一次确认,如果连续3次没有回应,连接会自动变成TcpState.Established。

    这里说一下,查询过程中发现很多人使用socket去poll来进行判断,在测试中,发现不好用,响应不及时,后来多方查找资料并测试,发现通过系统本身的连接来进行判断比较准确,方法如下:

    /// <summary>
            /// THIS FUNCTION WILL CHECK IF CLIENT IS STILL CONNECTED WITH SERVER.
            /// </summary>
            /// <returns>FALSE IF NOT CONNECTED ELSE TRUE</returns>
            public bool isClientConnected(TcpClient ClientSocket)
            {
                IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
    
                TcpConnectionInformation[] tcpConnections = ipProperties.GetActiveTcpConnections();
    
                foreach (TcpConnectionInformation c in tcpConnections)
                {
                    TcpState stateOfConnection = c.State;
    
                    if (c.LocalEndPoint.Equals(ClientSocket.Client.LocalEndPoint) && c.RemoteEndPoint.Equals(ClientSocket.Client.RemoteEndPoint))
                    {
                        if (stateOfConnection == TcpState.Established)
                        {
                            return true;
                        }
                        else
                        {
                            return false;
                        }
    
                    }
    
                }
    
                return false;
            }

    这样解决办法就简单了。

    单独写一个CheckAlive的线程进行检测,然后抛出事件并移除连接就ok。

    public void StartCheckAlive()
            {
                Thread th = new Thread(new ThreadStart(CheckAlive));
                th.IsBackground = true;
                th.Start();
                TCPLogger.Log("CheckAlive线程已启动");
            }
    
    private void CheckAlive()
            {
                Thread.Sleep(10000);
                while(isListen)
                {
                    try
                    {
                        lock (ClientList)
                        {
                            foreach (ClientItem item in ClientList)
                            {
                                //if (item.Client.Client.Poll(500, System.Net.Sockets.SelectMode.SelectRead) && (item.Client.Client.Available == 0))
    
                                if (!isClientConnected(item.Client))
                                {
                                    removeQueue.Enqueue(item);
                                    continue;
                                }
                            }
                            while (removeQueue.Count > 0)
                            {
                                ClientItem item = removeQueue.Dequeue();
                                clientList.Remove(item);
                                try
                                {
                                    TCPLogger.Log("关闭客户端连接");
                                    item.Client.Close();
    
    
                                }
                                catch (Exception ex)
                                {
                                    TCPLogger.Log("关闭客户端连接", ex);
                                }
                                TCPLogger.Log("CheckAlive移除链接:" + item.RegCode);
                                if (OnClientRemoved != null)
                                    OnClientRemoved(item.RegCode);
                            }
                        }
                        
                    }catch(Exception e)
                    {
                        TCPLogger.Log("CheckAlive异常.", e);
                    }
                    
                    Thread.Sleep(500);
                }
            }

    项目内容比较多,并且是公司项目,就不完整贴出来了,相信会对大家有帮助。
  • 相关阅读:
    View(视图)
    计算器 ——给按钮添加监听器
    Activity(活动)
    UI组件
    计算器界面——GridLayout
    信息界面——GridLayout
    Android计算器界面 TableLayout
    javaji基础x2
    java的开始
    数据类型
  • 原文地址:https://www.cnblogs.com/boentouch/p/12835453.html
Copyright © 2011-2022 走看看