切實解決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); } }

項目內容比較多,並且是公司項目,就不完整貼出來了,相信會對大家有幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM