本文摘自數組
http://www.z6688.com/info/57987-1.htm網絡
最近我負責一個IM項目的開發,服務端和客戶端採用TCP協議鏈接。服務端採用C#開發,客戶端採用Delphi開發。在服務端開發中我碰到了各類各樣的網絡異常斷開現象。在處理這些異常的時候有了一些心得,如今寫出來和你們分享一下。socket
那網絡異常斷開緣由主要有那些呢?概括起來主要有如下兩種:tcp
一、客戶端程序異常。函數
對於這種狀況,咱們很好處理,由於客戶端程序異常退出會在服務端引起ConnectionReset的Socket異常(就是WinSock2中的10054異常)。只要在服務端處理這個異常就能夠了。oop
二、網絡鏈路異常。ui
如:網線拔出、交換機掉電、客戶端機器掉電。當出現這些狀況的時候服務端不會出現任何異常。這樣的話上面的代碼就不能處理這種狀況了。對於這種狀況在MSDN裏面是這樣處理的,我在這裏貼出MSDN的原文:spa
若是您須要肯定鏈接的當前狀態,請進行非阻止、零字節的 Send 調用。若是該調用成功返回或引起 WAEWOULDBLOCK 錯誤代碼 (10035),則該套接字仍然處於鏈接狀態;不然,該套接字再也不處於鏈接狀態。操作系統
可是我在實際應用中發現,MSDN說的這種處理方法在不少時候根本無效,沒法檢測出網絡已經異常斷開了。那咱們該怎麼辦呢?htm
咱們知道,TCP有一個鏈接檢測機制,就是若是在指定的時間內(通常爲2個小時)沒有數據傳送,會給對端發送一個Keep-Alive數據報,使用的序列號是曾經發出的最後一個報文的最後一個字節的序列號,對端若是收到這個數據,回送一個TCP的ACK,確認這個字節已經收到,這樣就知道此鏈接沒有被斷開。若是一段時間沒有收到對方的響應,會進行重試,重試幾回後,向對端發一個reset,而後將鏈接斷掉。
在Windows中,第一次探測是在最後一次數據發送的兩個小時,而後每隔1秒探測一次,一共探測5次,若是5次都沒有收到迴應的話,就會斷開這個鏈接。但兩個小時對於咱們的項目來講顯然太長了。咱們必須縮短這個時間。那麼咱們該如何作呢?我要利用Socket類的IOControl()函數。咱們來看看這個函數能幹些什麼:
使用 IOControlCode 枚舉指定控制代碼,爲 Socket 設置低級操做模式。
命名空間:System.Net.Sockets
程序集:System(在 system.dll 中)
語法
C#
public int IOControl (
IOControlCode ioControlCode,
byte[] optionInValue,
byte[] optionOutValue
)
參數
ioControlCode
一個 IOControlCode 值,它指定要執行的操做的控制代碼。
optionInValue
Byte 類型的數組,包含操做要求的輸入數據。
optionOutValue
Byte 類型的數組,包含由操做返回的輸出數據。
返回值
optionOutValue 參數中的字節數。
如:
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
咱們要搞清楚的就是inOptionValues的定義,在C++裏它是一個結構體。咱們來看看這個結構體:
struct tcp_keepalive
...{
u_long onoff; //是否啓用Keep-Alive
u_long keepalivetime; //多長時間後開始第一次探測(單位:毫秒)
u_long keepaliveinterval; //探測時間間隔(單位:毫秒)
};
在C#中,咱們直接用一個Byte數組傳遞給函數:http://www.devdao.com/
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);//是否啓用Keep-Alive
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//多長時間開始第一次探測
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);//探測時間間隔
具體實現代碼:
public static void AcceptThread()
...{
Thread.CurrentThread.IsBackground = true;
while (true)
...{
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)5000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);
try
...{
Accept(inOptionValues);
}
catch ...{ }
}
}
private static void Accept(byte[] inOptionValues)
...{
Socket socket = Public.s_socketHandler.Accept();
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
UserInfo info = new UserInfo();
info.socket = socket;
int id = GetUserId();
info.Index = id;
Public.s_userList.Add(id, info);
socket.BeginReceive(info.Buffer, 0, info.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), info);
}
好了,這樣就成功了。
自我注:
使用此方法的確可以解決一些網絡情況較差的狀況下的資源管理問題,遠端Socket異常斷開後若是不及時回收資源,在.Net下資源耗費是很大的,使用此方法能較快的發現遠端的鏈接狀態,因此能及時回收一些無效鏈接以達到資源管理的目的.
附:
.Net2003下沒有IOControlCode的枚舉
.Net2005下IOControlCode枚舉是這樣的
// 摘要:
// 指定 System.Net.Sockets.Socket.IOControl(System.Int32,System.Byte[],System.Byte[])
// 方法支持的 IO 控制代碼。
public enum IOControlCode
{
// 摘要:
// 當傳入的消息隊列已滿時,將時間最久的已排隊數據報替換爲傳入的數據報。此值等於 Winsock 2 SIO_ENABLE_CIRCULAR_QUEUEING
// 常數。
EnableCircularQueuing = 671088642,
//
// 摘要:
// 放棄發送隊列的內容。此值等於 Winsock 2 SIO_FLUSH 常數。
Flush = 671088644,
//
// 摘要:
// 當套接字協議族的本地接口列表更改時啓用接收通知。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_ADDRESS_LIST_CHANGE
// 常數。
AddressListChange = 671088663,
//
// 摘要:
// 返回可讀取的字節數。此值等於 Winsock 2 FIONREAD 常數。
DataToRead = 1074030207,
//
// 摘要:
// 返回有關等待要接收的帶外數據的信息。在流式套接字上使用此控制代碼時,返回值指示可用的字節數。
OobDataRead = 1074033415,
//
// 摘要:
// 返回包含當前套接字地址族的廣播地址的 SOCKADDR 結構。返回的地址可與 Overload:System.Net.Sockets.Socket.SendTo
// 方法一塊兒使用。此值等於 Winsock 2 SIO_GET_BROADCAST_ADDRESS 常數。此值只能在用戶數據報協議 (UDP) 套接字上使用。
GetBroadcastAddress = 1207959557,
//
// 摘要:
// 返回套接字可綁定到的本地接口列表。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_ADDRESS_LIST_QUERY
// 常數。
AddressListQuery = 1207959574,
//
// 摘要:
// 檢索基礎提供程序的 SOCKET 句柄。此句柄可用於接收即插即用事件通知。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於
// Winsock 2 SIO_QUERY_TARGET_PNP_HANDLE 常數。
QueryTargetPnpHandle = 1207959576,
//
// 摘要:
// 當數據等待接收時啓用通知。此值等於 Winsock 2 FIOASYNC 常數。
AsyncIO = 2147772029,
//
// 摘要:
// 控制套接字的阻止行爲。若是使用此控制代碼指定的參數爲零,套接字將置於阻止模式下。若是參數不爲零,套接字將置於非阻止模式下。此值等於 Winsock 2
// FIONBIO 常數。
NonBlockingIO = 2147772030,
//
// 摘要:
// 將此套接字與附帶接口的指定句柄關聯。有關其餘詳細信息,請參考 Winsock 2 參考或文檔中特定附帶接口的相應協議特定附錄。建議使用組件對象模型
// (COM) 代替此 IOCTL,以發現並跟蹤套接字可能支持的其餘接口。此控制代碼是爲了與某些系統保持向後兼容而提供的,在這些系統中,COM 不可用或因爲某些其餘緣由而沒法使用。此值等於
// Winsock 2 SIO_ASSOCIATE_HANDLE 常數。
AssociateHandle = 2281701377,
//
// 摘要:
// 控制套接字發送的多路廣播數據是否在套接字接收隊列中顯示爲傳入數據。此值等於 Winsock 2 SIO_MULTIPOINT_LOOPBACK 常數。
MultipointLoopback = 2281701385,
//
// 摘要:
// 控制路由器能夠轉發多路廣播數據包的次數,也稱做生存時間 (TTL) 或躍點計數。此值等於 Winsock 2 SIO_MULTICAST_SCOPE
// 常數。
MulticastScope = 2281701386,
//
// 摘要:
// 設置套接字的服務質量 (QOS) 屬性。QOS 用於定義套接字的帶寬要求。Windows Me、Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於
// Winsock 2 SIO_SET_QOS 常數。
SetQos = 2281701387,
//
// 摘要:
// 設置套接字組的服務質量 (QOS) 屬性。此值保留供未來使用,而且等於 Winsock 2 SIO_SET_GROUP_QOS 常數。
SetGroupQos = 2281701388,
//
// 摘要:
// 當用於訪問遠程終結點的本地接口更改時啓用接收通知。此值等於 Winsock 2 SIO_ROUTING_INTERFACE_CHANGE 常數。
RoutingInterfaceChange = 2281701397,
//
// 摘要:
// 控制套接字是否在命名空間查詢無效時接收通知。Windows XP 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_NSP_NOTIFY_CHANGE
// 常數。
NamespaceChange = 2281701401,
//
// 摘要:
// 啓用對網絡上的全部 IPv4 數據包的接收。套接字必須有 System.Net.Sockets.AddressFamily.InterNetwork
// 地址族,套接字類型必須是 System.Net.Sockets.SocketType.Raw,而且協議類型必須爲 System.Net.Sockets.ProtocolType.IP。當前用戶必須屬於本地計算機上的
// Administrators 組,而且套接字必須綁定到特定端口。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2
// SIO_RCVALL 常數。
ReceiveAll = 2550136833,
//
// 摘要:
// 啓用對網絡上的全部多路廣播 IPv4 數據包的接收。這些數據包的目標地址範圍介於 224.0.0.0 到 239.255.255.255 之間。套接字必須有
// System.Net.Sockets.AddressFamily.InterNetwork 地址族,套接字類型必須是 System.Net.Sockets.SocketType.Raw,而且協議類型必須爲
// System.Net.Sockets.ProtocolType.Udp。當前用戶必須屬於本地計算機上的 Administrators 組,而且套接字必須綁定到特定端口。Windows
// 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_RCVALL_MCAST 常數。
ReceiveAllMulticast = 2550136834,
//
// 摘要:
// 啓用對網絡上的全部 Internet 組管理協議 (IGMP) 數據包的接收。套接字必須有 System.Net.Sockets.AddressFamily.InterNetwork
// 地址族,套接字類型必須是 System.Net.Sockets.SocketType.Raw,而且協議類型必須爲 System.Net.Sockets.ProtocolType.Igmp。當前用戶必須屬於本地計算機上的
// Administrators 組,而且套接字必須綁定到特定端口。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2
// SIO_RCVALL_IGMPMCAST 常數。
ReceiveAllIgmpMulticast = 2550136835,
//
// 摘要:
// 控制 TCP keep-alive 數據包的發送以及發送間隔。Windows 2000 及更高版本的操做系統支持此控制代碼。有關附加信息,請參見
// RFC 1122 的 4.2.3.6 節。此值等於 Winsock 2 SIO_KEEPALIVE_VALS 常數。
KeepAliveValues = 2550136836,
//
// 摘要:
// 此值等於 Winsock 2 SIO_ABSORB_RTRALERT 常數。
AbsorbRouterAlert = 2550136837,
//
// 摘要:
// 設置用於輸出的單播數據包的接口。此值等於 Winsock 2 SIO_UCAST_IF 常數。
UnicastInterface = 2550136838,
//
// 摘要:
// 此值等於 Winsock 2 SIO_LIMIT_BROADCASTS 常數。
LimitBroadcasts = 2550136839,
//
// 摘要:
// 將套接字綁定到指定的接口索引。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_INDEX_BIND
// 常數。
BindToInterface = 2550136840,
//
// 摘要:
// 設置用於輸出的多路廣播數據包的接口。該接口經過其索引進行標識。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2
// SIO_INDEX_MCASTIF 常數。
MulticastInterface = 2550136841,
//
// 摘要:
// 使用按索引標識的接口聯接多路廣播組。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_INDEX_ADD_MCAST
// 常數。
AddMulticastGroupOnInterface = 2550136842,
//
// 摘要:
// 將套接字從多路廣播組中移除。Windows 2000 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_INDEX_ADD_MCAST
// 常數。
DeleteMulticastGroupFromInterface = 2550136843,
//
// 摘要:
// 獲取提供程序特定的函數,這類函數不是 Winsock 規範的一部分。它們使用其提供程序分配的 GUID 進行指定。此值等於 Winsock 2 SIO_GET_EXTENSION_FUNCTION_POINTER
// 常數。
GetExtensionFunctionPointer = 3355443206,
//
// 摘要:
// 檢索與套接字關聯的 QOS 結構。只有能提供 QOS 傳輸的平臺(Windows Me、Windows 2000 和更高版本)才支持此控件。此值等於
// Winsock 2 SIO_GET_QOS 常數。
GetQos = 3355443207,
//
// 摘要:
// 返回套接字組的服務質量 (QOS) 屬性。此值保留供未來使用,而且等於 Winsock 2 SIO_GET_GROUP_QOS 常數。
GetGroupQos = 3355443208,
//
// 摘要:
// 返回附帶接口上下文中有效的套接字的句柄。此值等於 Winsock 2 SIO_TRANSLATE_HANDLE 常數。
TranslateHandle = 3355443213,
//
// 摘要:
// 返回可用於鏈接到指定遠程地址的接口地址。此值等於 Winsock 2 SIO_ROUTING_INTERFACE_QUERY 常數。
RoutingInterfaceQuery = 3355443220,
//
// 摘要:
// 對 System.Net.Sockets.IOControlCode.AddressListQuery 字段返回的結構進行排序,併爲 IPv6 地址添加範圍
// ID 信息。Windows XP 及更高版本的操做系統支持此控制代碼。此值等於 Winsock 2 SIO_ADDRESS_LIST_SORT 常數。
AddressListSort = 3355443225,
}
值爲Uint型,在調用的時候要轉換爲int型