socket粘包處理

using System;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
 
namespace Player.Common.Sockets
{
    /// <summary>
    /// Socket連接,雙向通信
    /// </summary>
    public class TcpSocketConnection
    {
        #region 構造函數
 
        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="socket">維護的Socket對象</param>
        /// <param name="server">維護此連接的服務對象</param>
        /// <param name="recLength">接受緩衝區大小</param>
        public TcpSocketConnection(Socket socket, TcpSocketServer server, int recLength)
        {
            _socket = socket;
            _server = server;
            _recLength = recLength;
        }
 
        #endregion
 
        #region 私有成員
 
        private Socket _socket { get; }
        private bool _isRec { getset; } = true;
        private TcpSocketServer _server { getset; } = null;
        private bool _isClosed { getset; } = false;
        private string _connectionId { getset; } = Guid.NewGuid().ToString();
 
        private ManualResetEvent allDone { getset; } = new ManualResetEvent(false);
        /// <summary>
        /// 接收區大小,單位:字節
        /// </summary>
        private int _recLength { getset; }
        #endregion
 
        #region 外部接口
 
        int headSize = 4;//包頭長度 固定4
        byte[] surplusBuffer = null;//不完整的數據包,即用戶自定義緩衝區
        /// <summary>
        /// 開始接受客戶端消息
        /// </summary>
        public void StartRecMsg()
        {
            try
            {
                while (true)
                {
                    allDone.Reset();
                    byte[] container = new byte[_recLength];
                    _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult =>
                    {
                        try
                        {
                            //bytes 爲系統緩衝區數據
                            //bytesRead爲系統緩衝區長度
                            int bytesRead = _socket.EndReceive(asyncResult);
                            if (bytesRead > 0)
                            {
                                if (surplusBuffer == null)//判斷是不是第一次接收,爲空說是第一次
                                {
                                    surplusBuffer = new byte[bytesRead];
                                    Array.Copy(container, 0, surplusBuffer, 0, bytesRead);
                                }
                                else
                                {
                                    byte[] container1 = new byte[bytesRead];
                                    Array.Copy(container, 0, container1, 0, bytesRead);
                                    surplusBuffer = surplusBuffer.Concat(container1).ToArray();//拼接上一次剩餘的包,已經完成讀取每個數據包長度
                                }
 
                                int haveRead = 0;
                                //這裏totalLen的長度有可能大於緩衝區大小的(因爲 這裏的surplusBuffer 是系統緩衝區+不完整的數據包)
                                int totalLen = surplusBuffer.Length;
                                while (haveRead <= totalLen)
                                {
                                    //如果在N此拆解後剩餘的數據包連一個包頭的長度都不夠
                                    //說明是上次讀取N個完整數據包後,剩下的最後一個非完整的數據包
                                    if (totalLen - haveRead < headSize)
                                    {
                                        byte[] byteSub = new byte[totalLen - haveRead];
                                        //把剩下不夠一個完整的數據包存起來
                                        Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
                                        surplusBuffer = byteSub;
                                        totalLen = 0;
                                        break;
                                    }
                                    //如果夠了一個完整包,則讀取包頭的數據
                                    byte[] headByte = new byte[headSize];
                                    Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//從緩衝區裏讀取包頭的字節
                                    int bodySize = BitConverter.ToInt32(headByte, 0);//從包頭裏面分析出包體的長度
 
                                    //這裏的 haveRead=等於N個數據包的長度 從0開始;0,1,2,3....N
                                    //如果自定義緩衝區拆解N個包後的長度 大於 總長度,說最後一段數據不夠一個完整的包了,拆出來保存
                                    if (haveRead + headSize + bodySize > totalLen)
                                    {
                                        byte[] byteSub = new byte[totalLen - haveRead];
                                        Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
                                        surplusBuffer = byteSub;
                                        break;
                                    }
                                    if (bodySize < 2)
                                    {
                                        WebLogHelper.Error(LogType.SysLog, $"bytesRead:{bytesRead},bodySize:{bodySize}");
                                        Close();
                                        break;
                                    }
                                    //處理正常消息
                                    byte[] recBytes = new byte[bodySize + headSize];
                                    Array.Copy(surplusBuffer, haveRead, recBytes, 0, bodySize + headSize);
                                    //取出消息內容
                                    HandleRecMsg?.Invoke(_server, this, recBytes);
                                    //依次累加當前的數據包的長度
                                    haveRead = haveRead + headSize + bodySize;
                                    if (headSize + bodySize == bytesRead)//如果當前接收的數據包長度正好等於緩衝區長度,則待拼接的不規則數據長度歸0
                                    {
                                        surplusBuffer = null;//設置空 回到原始狀態
                                        totalLen = 0;//清0
                                    }
                                }
                            }
                            //開始下一輪接收
                            if (_isRec && IsSocketConnected())
                            {
                                allDone.Set();
                                //StartRecMsg();
                            }
                        }
                        catch (ObjectDisposedException )
                        {
                        }
                        catch (Exception ex)
                        {
                            HandleException?.Invoke(ex);
                            Close();
                        }
                    }, null);
                    allDone.WaitOne();
                }
                 
            }
            catch (Exception ex)
            {
                HandleException?.Invoke(ex);
                Close();
            }
        }
 
        /// <summary>
        /// 發送數據
        /// </summary>
        /// <param name="bytes">數據字節</param>
        public void Send(byte[] bytes)
        {
            try
            {
                _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult =>
                {
                    try
                    {
                        int length = _socket.EndSend(asyncResult);
                        HandleSendMsg?.Invoke(_server, this, bytes);
                    }
                    catch (Exception ex)
                    {
                        HandleException?.Invoke(ex);
                    }
                }, null);
            }
            catch (Exception ex)
            {
                HandleException?.Invoke(ex);
            }
        }
 
        /// <summary>
        /// 發送字符串(默認使用UTF-8編碼)
        /// </summary>
        /// <param name="msgStr">字符串</param>
        public void Send(string msgStr)
        {
            Send(Encoding.UTF8.GetBytes(msgStr));
        }
 
        /// <summary>
        /// 發送字符串(使用自定義編碼)
        /// </summary>
        /// <param name="msgStr">字符串消息</param>
        /// <param name="encoding">使用的編碼</param>
        public void Send(string msgStr, Encoding encoding)
        {
            Send(encoding.GetBytes(msgStr));
        }
 
        /// <summary>
        /// 連接標識Id
        /// 注:用於標識與客戶端的連接
        /// </summary>
        public string ConnectionId
        {
            get
            {
                return _connectionId;
            }
            set
            {
                string oldConnectionId = _connectionId;
                _connectionId = value;
                _server?.SetConnectionId(this, oldConnectionId, value);
            }
        }
 
        /// <summary>
        /// 關閉當前連接
        /// </summary>
        public void Close()
        {
 
            WebLogHelper.Error(LogType.SysLog, "自動關閉");
            if (_isClosed)
                return;
            try
            {
                _isClosed = true;
                _server.RemoveConnection(this);
 
                _isRec = false;
                _socket.BeginDisconnect(false, (asyncCallback) =>
                {
                    try
                    {
                        _socket.EndDisconnect(asyncCallback);
                    }
                    catch (Exception ex)
                    {
                        HandleException?.Invoke(ex);
                    }
                    finally
                    {
                        _socket.Dispose();
                    }
                }, null);
            }
            catch (Exception ex)
            {
                WebLogHelper.Info(LogType.SysLog, "斷開連接報錯");
                HandleException?.Invoke(ex);
            }
            finally
            {
                try
                {
                    HandleClientClose?.Invoke(_server, this);
                }
                catch (Exception ex)
                {
                    WebLogHelper.Info(LogType.SysLog, "調用關閉事件報錯");
                    HandleException?.Invoke(ex);
                }
            }
        }
 
        /// <summary>
        /// 判斷是否處於已連接狀態
        /// </summary>
        /// <returns></returns>
        public bool IsSocketConnected()
        {
            return !((_socket.Poll(1000, SelectMode.SelectRead) && (_socket.Available == 0)) || !_socket.Connected);
        }
 
        #endregion
 
        #region 事件處理
 
        /// <summary>
        /// 客戶端連接接受新的消息後調用
        /// </summary>
        public Action<TcpSocketServer, TcpSocketConnection, byte[]> HandleRecMsg { getset; }
 
        /// <summary>
        /// 客戶端連接發送消息後回調
        /// </summary>
        public Action<TcpSocketServer, TcpSocketConnection, byte[]> HandleSendMsg { getset; }
 
        /// <summary>
        /// 客戶端連接關閉後回調
        /// </summary>
        public Action<TcpSocketServer, TcpSocketConnection> HandleClientClose { getset; }
 
        /// <summary>
        /// 異常處理程序
        /// </summary>
        public Action<Exception> HandleException { getset; }
 
        #endregion
    }
}

 

 
標籤: .Net
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章