C# + Socket斷線重連
阿新 • • 發佈:2019-01-25
轉自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28765492&id=3793532
感謝作者的分享,收藏
一、網上常用方法
1、當Socket.Conneted == false時,呼叫如下函式進行判斷
/// <summary> /// 當socket.connected為false時,進一步確定下當前連線狀態 /// </summary> /// <returns></returns> private bool IsSocketConnected() { #region remarks /******************************************************************************************** * 當Socket.Conneted為false時, 如果您需要確定連線的當前狀態,請進行非阻塞、零位元組的 Send 呼叫。 * 如果該呼叫成功返回或引發WAEWOULDBLOCK 錯誤程式碼 (10035),則該套接字仍然處於連線狀態; * 否則,該套接字不再處於連線狀態。 * Dependingonhttp://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connected.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2 ********************************************************************************************/ #endregion #region 過程 //This is how you can determine whether a socket is still connected. bool connectState = true; bool blockingState = socket.Blocking; try { byte[] tmp = new byte[1]; socket.Blocking = false; socket.Send(tmp, 0, 0); //Console.WriteLine("Connected!"); connectState = true; //若Send錯誤會跳去執行catch體,而不會執行其try體裡其之後的程式碼 } catch (SocketException e) { //10035 == WSAEWOULDBLOCK if (e.NativeErrorCode.Equals(10035)) { //Console.WriteLine("StillConnected, but the Send would block"); connectState = true; } else { //Console.WriteLine("Disconnected:error code {0}!", e.NativeErrorCode); connectState = false; } } finally { socket.Blocking = blockingState; } //Console.WriteLine("Connected:{0}", client.Connected); return connectState; #endregion }
2、根據socket.poll判斷
/// <summary> /// 另一種判斷connected的方法,但未檢測對端網線斷開或ungraceful的情況 /// </summary> /// <paramname="s"></param> /// <returns></returns> static bool IsSocketConnected(Socket s) { #region remarks /*As zendar wrote, it is nice to use the Socket.Poll and Socket.Available, butyou need to take intoconside ration *that the socket might not have been initialized in the first place. *This is the last (I believe) piece of information and it is supplied by theSocket.Connected property. *The revised version of the method would looks something like this: *from:http://stackoverflow.com/questions/2661764/how-to-check-if-a-socket-is-connected-disconnected-in-c*/ #endregion #region 過程 return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected); /*The long, but simpler-to-understand version: boolpart1 = s.Poll(1000, SelectMode.SelectRead); boolpart2 = (s.Available == 0); if((part1 && part2 ) || !s.Connected) returnfalse; else returntrue; */ #endregion }
總結:--1--此兩種方法出處可在函式體中的remark中找到連結
--2--此兩種方法適用於對端正常關閉socket下的本地socket狀態檢測,在非正常關閉如斷電、拔網線的情況下不起作用
因為Socket.Conneted存在bug,詳見.Net Bugs
二、支援物理斷線重連功能的類
利用BeginReceive + KeepAlive實現物理斷線重連,初步測驗了一下,正常。(部分程式碼參考帖子#26及blog在C#中利用keep-alive處理socket網路異常斷開)
Keep-Alive機制的介紹請看TCP Keepalive HOWTO
以此備忘,同時希望能幫助到有需要的同學。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace MySocket
{
public class Socket_wrapper
{
//委託
private delegate void delSocketDataArrival(byte[] data);
static delSocketDataArrival socketDataArrival = socketDataArrivalHandler;
private delegate void delSocketDisconnected();
static delSocketDisconnected socketDisconnected = socketDisconnectedHandler;
public static Socket theSocket = null;
private static string remoteHost = "192.168.1.71";
private static int remotePort = 6666;
private static String SockErrorStr = null;
private static ManualResetEvent TimeoutObject = new ManualResetEvent(false);
private static Boolean IsconnectSuccess = false; //非同步連線情況,由非同步連接回調函式置位
private static object lockObj_IsConnectSuccess = new object();
/// <summary>
/// 建構函式
/// </summary>
/// <param name="strIp"></param>
/// <param name="iPort"></param>
public Socket_wrapper(string strIp, int iPort)
{
remoteHost = strIp;
remotePort = iPort;
}
/// <summary>
/// 設定心跳
/// </summary>
private static void SetXinTiao()
{
//byte[] inValue = new byte[] { 1, 0, 0, 0, 0x20, 0x4e, 0, 0, 0xd0, 0x07, 0, 0 };// 首次探測時間20 秒, 間隔偵測時間2 秒
byte[] inValue = new byte[] { 1, 0, 0, 0, 0x88, 0x13, 0, 0, 0xd0, 0x07, 0, 0 };// 首次探測時間5 秒, 間隔偵測時間2 秒
theSocket.IOControl(IOControlCode.KeepAliveValues, inValue, null);
}
/// <summary>
/// 建立套接字+非同步連線函式
/// </summary>
/// <returns></returns>
private static bool socket_create_connect()
{
IPAddress ipAddress = IPAddress.Parse(remoteHost);
IPEndPoint remoteEP = new IPEndPoint(ipAddress, remotePort);
theSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
theSocket.SendTimeout = 1000;
SetXinTiao();//設定心跳引數
#region 非同步連線程式碼
TimeoutObject.Reset(); //復位timeout事件
try
{
theSocket.BeginConnect(remoteEP, connectedCallback, theSocket);
}
catch (Exception err)
{
SockErrorStr = err.ToString();
return false;
}
if (TimeoutObject.WaitOne(10000, false))//直到timeout,或者TimeoutObject.set()
{
if (IsconnectSuccess)
{
return true;
}
else
{
return false;
}
}
else
{
SockErrorStr = "Time Out";
return false;
}
#endregion
}
/// <summary>
/// 同步receive函式
/// </summary>
/// <param name="readBuffer"></param>
/// <returns></returns>
public string socket_receive(byte[] readBuffer)
{
try
{
if (theSocket == null)
{
socket_create_connect();
}
else if (!theSocket.Connected)
{
if (!IsSocketConnected())
Reconnect();
}
int bytesRec = theSocket.Receive(readBuffer);
if (bytesRec == 0)
{
//warning 0 bytes received
}
return Encoding.ASCII.GetString(readBuffer, 0, bytesRec);
}
catch (SocketException se)
{
//print se.ErrorCode
throw;
}
}
/// <summary>
/// 同步send函式
/// </summary>
/// <param name="sendMessage"></param>
/// <returns></returns>
public bool socket_send(string sendMessage)
{
if (checkSocketState())
{
return SendData(sendMessage);
}
return false;
}
/// <summary>
/// 斷線重連函式
/// </summary>
/// <returns></returns>
private static bool Reconnect()
{
//關閉socket
theSocket.Shutdown(SocketShutdown.Both);
theSocket.Disconnect(true);
IsconnectSuccess = false;
theSocket.Close();
//建立socket
return socket_create_connect();
}
/// <summary>
/// 當socket.connected為false時,進一步確定下當前連線狀態
/// </summary>
/// <returns></returns>
private bool IsSocketConnected()
{
#region remarks
/********************************************************************************************
* 當Socket.Conneted為false時, 如果您需要確定連線的當前狀態,請進行非阻塞、零位元組的 Send 呼叫。
* 如果該呼叫成功返回或引發 WAEWOULDBLOCK 錯誤程式碼 (10035),則該套接字仍然處於連線狀態;
* 否則,該套接字不再處於連線狀態。
* Depending on http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connected.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2
********************************************************************************************/
#endregion
#region 過程
// This is how you can determine whether a socket is still connected.
bool connectState = true;
bool blockingState = theSocket.Blocking;
try
{
byte[] tmp = new byte[1];
theSocket.Blocking = false;
theSocket.Send(tmp, 0, 0);
//Console.WriteLine("Connected!");
connectState = true; //若Send錯誤會跳去執行catch體,而不會執行其try體裡其之後的程式碼
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
{
//Console.WriteLine("Still Connected, but the Send would block");
connectState = true;
}
else
{
//Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode);
connectState = false;
}
}
finally
{
theSocket.Blocking = blockingState;
}
//Console.WriteLine("Connected: {0}", client.Connected);
return connectState;
#endregion
}
/// <summary>
/// 另一種判斷connected的方法,但未檢測對端網線斷開或ungraceful的情況
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static bool IsSocketConnected(Socket s)
{
#region remarks
/* As zendar wrote, it is nice to use the Socket.Poll and Socket.Available, but you need to take into consideration
* that the socket might not have been initialized in the first place.
* This is the last (I believe) piece of information and it is supplied by the Socket.Connected property.
* The revised version of the method would looks something like this:
* from:http://stackoverflow.com/questions/2661764/how-to-check-if-a-socket-is-connected-disconnected-in-c */
#endregion
#region 過程
if (s == null)
return false;
return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected);
/* The long, but simpler-to-understand version:
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if ((part1 && part2 ) || !s.Connected)
return false;
else
return true;
*/
#endregion
}
/// <summary>
/// 非同步連接回調函式
/// </summary>
/// <param name="iar"></param>
static void connectedCallback(IAsyncResult iar)
{
#region <remarks>
/// 1、置位IsconnectSuccess
#endregion </remarks>
lock (lockObj_IsConnectSuccess)
{
Socket client = (Socket)iar.AsyncState;
try
{
client.EndConnect(iar);
IsconnectSuccess = true;
StartKeepAlive(); //開始KeppAlive檢測
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
SockErrorStr = e.ToString();
IsconnectSuccess = false;
}
finally
{
TimeoutObject.Set();
}
}
}
/// <summary>
/// 開始KeepAlive檢測函式
/// </summary>
private static void StartKeepAlive()
{
theSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(OnReceiveCallback), theSocket);
}
/// <summary>
/// BeginReceive回撥函式
/// </summary>
static byte[] buffer = new byte[1024];
private static void OnReceiveCallback(IAsyncResult ar)
{
try
{
Socket peerSock = (Socket)ar.AsyncState;
int BytesRead = peerSock.EndReceive(ar);
if (BytesRead > 0)
{
byte[] tmp = new byte[BytesRead];
Array.ConstrainedCopy(buffer, 0, tmp, 0, BytesRead);
if (socketDataArrival != null)
{
socketDataArrival(tmp);
}
}
else//對端gracefully關閉一個連線
{
if (theSocket.Connected)//上次socket的狀態
{
if (socketDisconnected != null)
{
//1-重連
socketDisconnected();
//2-退出,不再執行BeginReceive
return;
}
}
}
//此處buffer似乎要清空--待實現 zq
theSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(OnReceiveCallback), theSocket);
}
catch (Exception ex)
{
if (socketDisconnected != null)
{
socketDisconnected(); //Keepalive檢測網線斷開引發的異常在這裡捕獲
return;
}
}
}
/// <summary>
/// 非同步收到訊息處理器
/// </summary>
/// <param name="data"></param>
private static void socketDataArrivalHandler(byte[] data)
{
}
/// <summary>
/// socket由於連線中斷(軟/硬中斷)的後續工作處理器
/// </summary>
private static void socketDisconnectedHandler()
{
Reconnect();
}
/// <summary>
/// 檢測socket的狀態
/// </summary>
/// <returns></returns>
public static bool checkSocketState()
{
try
{
if (theSocket == null)
{
return socket_create_connect();
}
else if (IsconnectSuccess)
{
return true;
}
else//已建立套接字,但未connected
{
#region 非同步連線程式碼
TimeoutObject.Reset(); //復位timeout事件
try
{
IPAddress ipAddress = IPAddress.Parse(remoteHost);
IPEndPoint remoteEP = new IPEndPoint(ipAddress, remotePort);
theSocket.BeginConnect(remoteEP, connectedCallback, theSocket);
SetXinTiao();//設定心跳引數
}
catch (Exception err)
{
SockErrorStr = err.ToString();
return false;
}
if (TimeoutObject.WaitOne(2000, false))//直到timeout,或者TimeoutObject.set()
{
if (IsconnectSuccess)
{
return true;
}
else
{
return false;
}
}
else
{
SockErrorStr = "Time Out";
return false;
}
#endregion
}
}
catch (SocketException se)
{
SockErrorStr = se.ToString();
return false;
}
}
/// <summary>
/// 同步傳送
/// </summary>
/// <param name="dataStr"></param>
/// <returns></returns>
public static bool SendData(string dataStr)
{
bool result = false;
if (dataStr == null || dataStr.Length < 0)
return result;
try
{
byte[] cmd = Encoding.Default.GetBytes(dataStr);
int n = theSocket.Send(cmd);
if (n < 1)
result = false;
}
catch (Exception ee)
{
SockErrorStr = ee.ToString();
result = false;
}
return result;
}
}
}