Socket套接字連接狀態判斷,接收數據筆記


  最近工作中涉汲到一些Socket 方面應用 ,如斷線重連,連接狀態判斷等,今天做了一些總結。

1.判斷Socket 連接狀態

通過 Poll 與 Connected 結合使用 ,重點關注 SelectRead  模式

方法名:

Socket.Poll (int microSeconds, System.Net.Sockets.SelectMode mode) 方法參數:

參數枚舉:

SelectRead 如果已調用 Listen(Int32) 並且有掛起的連接,則為 true。 - 或 - 如果有數據可供讀取,則為 true。 - 或 - 如果連接已關閉、重置或終止,則返回 true; 否則,返回 false。
SelectWrite 如果正在處理 Connect(EndPoint) 並且連接已成功,則為 true; - 或 - 如果可以發送數據,則返回 true; 否則,返回 false。
SelectError 如果正在處理不阻止的 Connect(EndPoint),並且連接已失敗,則為 true; - 或 - 如果 OutOfBandInline 未設置,並且帶外數據可用,則為 true; 否則,返回 false。

 

SelectRead 模式返回 true 的三種情況,若Socket 處掛起狀態 ,同時沒有數據可讀取,可以確定Socket 是關閉或終止的。

1.已調用 Listen(Int32) 並且有掛起的連接,則為 true

2.有數據可供讀取,則為 true

3.如果連接已關閉、重置或終止,則返回 true

 

如下代碼 ,true 表示連接斷開

s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)  || !s.Connected

稍作調整,判斷連接狀態完整代碼如下:

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
        }

 

2. Socket Blocking 屬性

默認情況 Blocking 為 true ,即阻塞狀態, Read 時 若沒收到數據 , Socket 會掛起當前線程,直到收到數據,當前線程才被喚醒。適用於接收到數據完成后再進行下一步操作場景 。 反之Blocking 為 false ,調用 Recevied 后,無論是否收到數據,都立即返回結果,當前線程可以繼續執行。

MSDN 強調, Blocking 屬性值與異步操作 如 (Begin 開始API) BeginReceive 等沒有相關性 , 比如通過 BeginReceive   接收數據,同時希望當前線程掛起,看起來,似乎可將 Blocking設為 true 實現 。其實不然,需要通過信號量 ManualResetEvent 實現掛起操作。

The Blocking property has no effect on asynchronous methods. If you are sending and receiving data asynchronously and want to block execution, use the ManualResetEvent class

 

3. Socket 發送數據時,自動斷線重連 

如添加心跳機制。 這里另一個簡單方案,在發送數據時,檢查連接狀態,如發現斷開,重新建立連接 , 這里 Socket 操作 加了鎖,防止並發操作。  

public static bool SendServer(string st)//發送
        {
            lock (SocketLock)
            {
                try
                {
                    if (SocketClient == null || !SocketClient.Connected) {
                        //斷開連接
                        if (SocketClient != null)
                            CloseSocket(SocketClient);

                        //重新連接
                        if (!TcpServer.TcpIpConnect(SystemConfig.TcpIP, SystemConfig.Port)) {
                            return false;
                        }
                    }

                    System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
                    watch.Start();

                    byte[] Msg = SendEncoding.GetBytes(st);
                    SocketClient.Send(Msg);

                    //接收相機返回結果
                    byte[] bytResult;
                    Receive(SocketClient, socketBuffer, 0, EndMathFormat, SocketClient.ReceiveTimeout, out bytResult);

                    watch.Stop();

                    Logger.Info("獲取相機坐標信息耗時(ms):" + watch.ElapsedMilliseconds);

                    string ReciveMsg = ReceiveEncoding.GetString(bytResult).TrimEnd(new char[] { '\n' , '\0'});
                    CameraBuding.ReciveMsg = ReciveMsg;
                    CameraPosition.BuDing = ReciveMsg;

                    Task.Factory.StartNew((s) => { MyEvent(s.ToString()); } , ReciveMsg);

                    return true;
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, " 發送消息給相機用來切換功能異常");
                    if (!IsSocketConnected(SocketClient))
                    {
                        //斷開連接
                        CloseSocket(SocketClient);

                        //重新連接
                        TcpServer.TcpIpConnect(SystemConfig.TcpIP, SystemConfig.Port);
                    }
                }

                return false;
            }
        }

 

4. Socket 接收數據場景

通常兩種場景,如:

a.接收指定長度數據包。

b.接收不定長數據包,根據終止符結束。(不定長數據包以 末尾  \n \0 做為結束標記 , 本示例可能返回包含多條終止符數據 ,需對結果二次分隔,如 abc\n\0def\n\0)

=>定長數據包接收:

/// <summary>
        /// socket 接收定長數據
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="size"></param>
        /// <param name="timeout"></param>
        public static void Receive(Socket socket, byte[] buffer, int offset, int size, int timeout)
        {
            int endTickCount = Environment.TickCount+ timeout;
            int received = 0;  // how many bytes is already received
            int errTimes = 0;
            do
            {
                if (Environment.TickCount > endTickCount)
                    throw new Exception("Receive Timeout.");
                try
                {
                    received += socket.Receive(buffer, offset + received, size - received, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        if (++errTimes > 3)
                            throw new Exception("Receive errTimes 引發異常");

                        // socket buffer is probably empty, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                        throw ex;  // any serious error occurr
                }
            } while (received < size);
        }

 

=>不定長數據包接收:

public static void Receive(Socket socket, byte[] buffer, int offset, byte [] endMath , int timeout  , out byte [] bytResult)
        {
            int endTickCount = Environment.TickCount + timeout ;
            int received = 0;  // how many bytes is already received

            int size = buffer.Length;
            bool isMatch = false;
            int errTimes = 0;
            int j = 0;
            do
            {
                if (Environment.TickCount > endTickCount)
                    throw new Exception("Receive Timeout.");
                try
                {
                    received += socket.Receive(buffer, offset + received, size - received, SocketFlags.None);

                    isMatch = true;
                    j = 0;
                    for (int i = received - endMath.Length; i < received && i >= 0; i++)
                    {
                        if (buffer[i] != endMath[j++])
                        {
                            isMatch = false;
                            break;
                        }
                    }

                    if (isMatch)
                    {
                        break;
                    }

                    if (received >= size)
                    {
                        throw new Exception("Receive 結束符非預期!");
                    }
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        if (++errTimes > 3)
                            throw new Exception("Receive errTimes 引發異常");

                        // socket buffer is probably empty, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                        throw ex;  // any serious error occurr
                }
            } while (true);

            bytResult = new byte[received];

            Array.Copy(buffer, 0, bytResult, 0, received);
        }

 

5. 關閉 Socket

private static void CloseSocket(Socket socket)
        {
            try
            {
                if (socket != null)
                {
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "關閉 socket 時 發生異常:" + ex.Message);
            }
            finally {
                socket = null;
            }
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM