切实解决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); } }
项目内容比较多,并且是公司项目,就不完整贴出来了,相信会对大家有帮助。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步