作者:RazanPaul
譯者:Todd Wei
原文:http://www.codeproject.com/KB/IP/TimeOutSocket.aspx
介紹
您可能注意到了,.Net的System.Net.Sockets.TcpClient和System.Net.Sockets.Socket都沒有直接為Connect/BeginConnect提供逾時控制機制。因此,當伺服器未處於監聽狀態,或者發生網路故障時,用戶端串連請求會被迫等待很長一段時間,直到拋出異常。預設的等待時間長達20~30s。.Net Socket庫的SocketOptionName.SendTimeout提供了控制發送資料的逾時時間,但並非本文討論的串連請求的逾時時間。
背景
這個問題最初源於我的某個項目,在解決以後,我曾將關鍵代碼發表在自己的部落格上。我注意到不少人對此表示感謝,所以我想這是一個常見的問題,或許很多人都需要解決它。
實現
下面是實現的關鍵代碼:
class TimeOutSocket
{
private static bool IsConnectionSuccessful = false;
private static Exception socketexception;
private static ManualResetEvent TimeoutObject = new ManualResetEvent(false);
public static TcpClient Connect(IPEndPoint remoteEndPoint, int timeoutMSec)
{
TimeoutObject.Reset();
socketexception = null;
string serverip = Convert.ToString(remoteEndPoint.Address);
int serverport = remoteEndPoint.Port;
TcpClient tcpclient = new TcpClient();
tcpclient.BeginConnect(serverip, serverport,
new AsyncCallback(CallBackMethod), tcpclient);
if (TimeoutObject.WaitOne(timeoutMSec, false))
{
if (IsConnectionSuccessful)
{
return tcpclient;
}
else
{
throw socketexception;
}
}
else
{
tcpclient.Close();
throw new TimeoutException("TimeOut Exception");
}
}
private static void CallBackMethod(IAsyncResult asyncresult)
{
try
{
IsConnectionSuccessful = false;
TcpClient tcpclient = asyncresult.AsyncState as TcpClient;
if (tcpclient.Client != null)
{
tcpclient.EndConnect(asyncresult);
IsConnectionSuccessful = true;
}
}
catch (Exception ex)
{
IsConnectionSuccessful = false;
socketexception = ex;
}
finally
{
TimeoutObject.Set();
}
}
}
這裡,ManualResetEvent的WaitOne(TimeSpan, Boolean)起到了主要的作用。它將阻止當前線程,直到ManualResetEvent對象被Set或者超過timeout時間。上面的代碼中,調用BeginConnect後通過WaitOne方法阻止當前線程,如果在timeoutMSec時間內串連成功,將在CallBackMethod回調中調用TimeoutObject.Set,解除被阻塞的連接線程並返回;否則,連接線程會在等待逾時後,主動關閉串連並拋出TimeoutException。
總結
雖然實現非常簡單,但或許很多人都需要串連請求逾時機制,如果有任何問題,我會儘力為您解答。
[譯註]
作者介紹了一種非同步串連+WaitOne的串連請求逾時機制。其中的實現細節有值得商榷的地方,比如:a.static成員帶來的執行緒安全性問題;b.可以考慮利用IAsyncResult.AsyncWaitHandle,不必另行建立ManualResetEvent。但瑕不掩瑜,感謝作者的解決思路。