1. 程式人生 > >C# + Socket斷線重連

C# + Socket斷線重連

轉自: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實現物理斷線重連,初步測驗了一下,正常。(部分程式碼參考帖子#26blog在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;
        }
    }
}