版權聲明:本文為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);
}
}
