C# Socket keeplive 心跳檢測實例


版權聲明:本文為CSDN博主「b哈利路亞d」的原創文章,重新編輯發布,請尊重原作者的勞動成果,轉載的時候附上原文鏈接:https://blog.csdn.net/lanwilliam/article/details/51698807

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