切实解决socket连接掉线检测

原文:切实解决socket连接掉线检测

新公司在做物联网,要做与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);
            }
        }

项目内容比较多,并且是公司项目,就不完整贴出来了,相信会对大家有帮助。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券