做socket保持長連接的一些心得 - chen_scor的日志 - 網易博客
http://www.dnbcw.com/biancheng/c/hgdc106706.html
最近做socket保持長連接的一些心得,歡迎大家討論 - .NET技術 / C#文章來源網絡 屬於c/c++分類 電腦編程網整理 2010/5/25
簡介:這是最近做socket保持長連接的一些心得,歡迎大家討論 - .NET技術 / C#的詳細頁面,介紹了和c/c++,最近做socket保持長連接的一些心得,歡迎大家討論 - .NET技術 / C#有關的知識,加入收藏請按鍵盤ctrl+D,謝謝大家的觀看!要查看更多有關信息,請點擊此處
自己寫的客戶端馬上要發布了,忽然發現了一大堆問題,主要集中在與服務器的TCP連接經常莫名斷開,客戶端又檢測不到,不能及時重連。一個多星期的修改,有一些心得,與大家分享。也希望大家多發表意見,您的意見也許最后就實現在我的軟件中了!
主要分為兩部分:
一,如何更好的檢測TCP連接是否正常
二,如何提取本機TCP連接狀態一,如何更好的檢測TCP連接是否正常
這方面問題,我上網查了很久,一般來說比較成熟的有兩種方法:
1是在應用層制定協議,發心跳包,這也是C#,JAVA等高級語言比較常用的方法。客戶端和服務端制定一個通訊協議,每隔一定時間(一般15秒左右),由一方發起,向對方發送協議包;對方收到這個包后,按指定好的通訊協議回一個。若沒收到回復,則判斷網絡出現問題,服務器可及時的斷開連接,客戶端也可以及時重連。
2通過TCP協議層發送KeepAlive包。這個方法只需設置好你使用的TCP的KeepAlive項就好,其他的操作系統會幫你完成。操作系統會按時發送KeepAlive包,一發現網絡異常,馬上斷開。我就是使用這個方法,也是重點向大家介紹的。使用第二種方法的好處,是我們在應用層不需自己定協議,通信的兩端,只要有一端設好這個值,兩邊都能及時檢測出TCP連接情況。而且這些都是操作系統幫你自動完成的。像我們公司的服務端代碼就是早寫好的,很難改動。以前也沒加入心跳機制,后面要改很麻煩,boss要求檢測連接的工作盡量客戶端單獨完成....
還有一個好處就是節省網絡資源。KeepAlive包,只有很簡單的一些TCP信息,無論如何也是比你自己設計的心跳包短小的。然后就是它的發送機制,在TCP空閑XXX秒后才開始發送。自己設計心跳機制的話,很難做到這一點。這種方法也是有些缺陷的。比如某一時刻,網線松了,如果剛好被KeepAlive包檢測到,它會馬上斷開TCP連接。但其實這時候TCP連接也算是established的,只要網線再插好,這個連接還是可以正常工作的。這種情況,大家有什么好辦法處理嗎?
C#中設置KeepAlive的代碼
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);IPEndPoint iep = new IPEndPoint(this._IPadd, xxxx);
this._socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
this._socket.Connect(iep);
這里我設定TCP15秒鍾空閑,就開始發送KeepAlive包,其實完全可是設定得長一點。
二,如何提取本機TCP連接狀態
設好了KeepAlive值,又遇到麻煩了,我沒找到當網絡異常時,它斷開連接后怎么通知我...我搜了很久都沒找到,要是哪位兄弟知道的話告訴我吧。我是使用笨辦法的,找到所有本地TCP連接的信息,篩選出我需要的那個TCP。
查看本機所有TCP連接信息,網上一般的方法,都是通過程序調用CMD命令里的netstat進行,然后再分析其內容。但在CMD窗口用過這個命令的都知道,悲劇的時候它顯示完所有TCP信息需要15s,或者更長時間,這在我的程序中是不能忍受的。
然后我又查找了一些牛人的博客,發現有人提到用iphlpapi.dll。這是一個在win98以上操作系統目錄System32都包含的庫函數,功能異常強大,大家可以放心使用!但是使用起來比較麻煩,基本找不到C#現成使用的例子,就算有,也是很老版本的,完全不能用
我參考了這兩位高人的博客
http://blog.csdn.net/yulinlover/archive/2009/02/08/3868824.aspx
(另一位的博客連接找不到了..悲劇啊!)
下載了里面提到的項目,仔細結合自己體會進行修改,終於能用了。每隔一段時間,我的客戶端就用這個方法掃描一遍本地TCP信息,若發現連接有問題,則斷開重連。
這個方法能瞬間得到本機所有TCP連接信息(如果你有興趣可以擴充,它的功能真的是太強大了),沒有CMD命令netstat那不能忍受的延遲,相當好用。代碼比較長,就不貼出來了。
這些是我不太成熟的做法,下星期項目就要提交了,不能再出啥岔子,希望大家多提意見,幫我改善一下。
本版人氣很旺,但貌似用socket的人不多,不知道帖子發這是否合適。要是不合適,請前輩提點下發在哪個版比較好?
回答 1--------------------------------------------------------------------------------
------其他回答(15分)---------我用的下面的代碼
C# code
#region 檢測網絡狀態
[DllImport("sensapi.dll")]
private extern static bool IsNetworkAlive(out int connectionDescription);private void NetCheckThread()
{
while (true)
{
int flags;//上網方式
bool m_bOnline = true;//是否在線
m_bOnline = IsNetworkAlive(out flags);
if (m_bOnline)
{
if (!NetAlive)
{
NetAlive = true;
try
{
if (NetConnect != null) NetConnect(this, null);
}
catch { }
}
}
else
{
if (NetAlive)
{
NetAlive = false;
try
{
if (NetDisconnect != null) NetDisconnect(this, null);
}
catch { }
}
}Thread.Sleep(1);
}
}#endregion
------其他回答(40分)---------
C# codepublic AsyncSocket(Socket sock,int index)
{
this.index = index;
this.sock = sock;
this.SetXinTiao(this.sock);
Socket obj_Socket = sock;
StateObject obj_SocketState = new StateObject();
obj_SocketState.workSocket = obj_Socket;
obj_Socket.BeginReceive(obj_SocketState.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), obj_SocketState);
}//設置心跳
private void SetXinTiao(Socket tmpsock)
{
byte[] inValue = new byte[] { 1, 0, 0, 0, 0x20, 0x4e, 0, 0, 0xd0, 0x07, 0, 0 };// 首次探測時間20 秒, 間隔偵測時間2 秒
tmpsock.IOControl(IOControlCode.KeepAliveValues, inValue, null);
}private void ReceiveCallback(IAsyncResult ar)
{
try
{
StateObject obj_SocketState = (StateObject)ar.AsyncState;
Socket obj_Socket = obj_SocketState.workSocket;
int BytesRead = obj_Socket.EndReceive(ar);
if (BytesRead > 0)
{
byte[] tmp = new byte[BytesRead];
Array.ConstrainedCopy(obj_SocketState.buffer, 0, tmp, 0, BytesRead);
if (socketDataArrival!=null)
{
socketDataArrival(this.index, tmp);
}
}
else
{
if (this.sock.Connected)
{
if (socketDisconnected!=null)
{
socketDisconnected(index);
}
}
}
obj_Socket.BeginReceive(obj_SocketState.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), obj_SocketState);
}
catch (Exception ex)
{
if (socketDisconnected!=null)
{
socketDisconnected(index); //Keepalive檢測斷線引發的異常在這里捕獲
}
}
}
------其他回答(10分)---------private void ReceiveCallBack(IAsyncResult ar)
{
try
{
lock (this)
{
int recvCount = _client.GetStream().EndRead(ar);//這種情況就是斷開了連接
if (recvCount < 1)
{
if (OnSocketError != null)
{
_client.Close();
OnSocketError(this, new EventArgs());
}
}------其他回答(15分)---------
引用 44 樓 cqsfd 的回復:
引用 34 樓 qldsrx 的回復:
討論了這么多,怎么就不考慮下不保持長連接的做法呢?服務器資源畢竟寶貴,如果說長時間不使用的連接也保持的話,顯然是不值得的,你必須明白,保持一個長連接的開銷比創建一個新連接更大,所以一般設置一個超時時間,當在規定時間內沒有動作就自動斷開連接,FTP就有這種設置。歡迎青龍白虎兄!
這個長連接是必須的,40樓的MM幫我說明了理由
很多情況下,是需要服務器給客戶端發信息的
如果用短連接,就得客戶端每隔個30秒連接一下服務端,看看服務端有消息要發沒,沒有就斷開;隔30秒再這么做一次
我的軟件剛好就有這個需求,所有得長連接
你說的是雙工通訊的情況,顯然這不是很多情況下都需要的,是個特例,不過這種特例也只需要一個長連接即可,並發的其它連接都應該使用短連接。另外你可以學學人家騰訊公司,他們就連一個長連接都沒有就可以得到服務器的通知,你知道這是什么原因嗎?因為UDP根本不需要連接,客戶端定時發送UDP包到服務器端確認是否有消息需要接收,這個確認包的開銷遠比TCP小的多,顯然這種確認包是否丟失都無所謂,你可以把頻率放高,一旦有消息需要接收,也可以切換到TCP連接去服務器讀取消息。
最后,如果條件允許,開始用WCF來實現遠程通訊吧,畢竟WCF的功能比Socket強大得多。
------其他回答(20分)---------
好像你沒有回答我的問題啊!
==============================
呃,我是看這個地方熱鬧,湊個熱鬧讓大家看看我的做法合理不。我推測,網絡斷開,你能知道,是因為對方給你發了個TCP的FIN包,這個包是沒內容的
要是客戶端網線把了一會,你能檢測出來嗎?2個小時以上,沒任何消息的TCP,還能保持嗎?
====================================================================
這個我試了下,如果客戶端把無線網卡關閉,服務端用這種方法是監測不到客戶端的斷開動作。到底為什么會有if (_readBytesCount == 0) ?
===============================================
_readBytesCount是NetworkSream.Read方法返回值。如果客戶端close。則此方法立即返回0。
