c#Socket服務端異步高性能并發(fā) 解決 半包 粘包問題 封裝類

/// <summary>
/// Socket連接,雙向通信
/// </summary>
public class SocketConnection
{
    #region 構造函數(shù)

    public SocketConnection(Socket socket, SocketServer server)
    {
        _socket = socket;
        _server = server;
    }

    #endregion

    public Socket ClientSocket { get { return _socket; } }
    public bool IsCheck { get; set; }//該客戶是否通過首次驗證  否則無法進行后續(xù)操作
    #region 私有成員

    private readonly Socket _socket;
    private bool _isRec = true;
    private SocketServer _server = null;
    private byte[] Tmp_byteArr = new byte[0];//緩存 多余 或者 不完整 的封包數(shù)據(jù)
    private bool IsSocketConnected()
    {
        bool part1 = _socket.Poll(1000, SelectMode.SelectRead);
        bool part2 = (_socket.Available == 0);
        if (part1 && part2)
            return false;
        else
            return true;
    }

    #endregion

    #region 外部接口

    /// <summary>
    /// 開始接受客戶端消息
    /// </summary>
    public void StartRecMsg()
    {
        try
        {
            byte[] container = new byte[1024 * 1024 * 2];
            _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult =>
            {
                try
                {
                    int length = _socket.EndReceive(asyncResult);

                    //馬上進行下一輪接受而叼,增加吞吐量
                    if (length > 0 && _isRec && IsSocketConnected())
                        StartRecMsg();

                    if (length > 0)
                    {
                        byte[] recBytes = new byte[length];
                        Array.Copy(container, 0, recBytes, 0, length);
                        ParMsg(recBytes);

                        ////處理消息 這里我把他取消了 放到了下面消息解析里面觸發(fā) 當解析出完整數(shù)據(jù)時再觸發(fā) 這樣訂閱事件的地方不用在做處理
                        //HandleRecMsg?.Invoke(recBytes, this, _server);
                    }
                    else
                        Close();
                }
                catch (Exception ex)
                {
                    HandleException?.Invoke(ex);
                    Close();
                }
            }, null);
        }
        catch (Exception ex)
        {
            HandleException?.Invoke(ex);
            Close();
        }
    }

    void ParMsg(byte[] arr)
    {
        List<byte> result = new List<byte>();//之后所有的操作數(shù)據(jù)都保存在這  list方便點
        if (Tmp_byteArr.Length > 0)//判斷之前是否有保存多余數(shù)據(jù)
        {
            result.AddRange(Tmp_byteArr);
        }
        result.AddRange(arr);
        if (result.Count <= 4)//前4個字節(jié)代表包長 如果不夠則表示包不完整 保存起來
        {
            Tmp_byteArr = result.ToArray();
            return;
        }
        int DataLength = BitConverter.ToInt32(Common.SubByte(result.ToArray(), 0, 4), 0);//包長 但不含這個包長本身
        if (DataLength == result.Count - 4)//如果相等 則剛好是一個完整的包
        {
            MsgBody msg = new MsgBody(result.ToArray());
            //處理消息 
            HandleRecMsg?.Invoke(msg, this, _server);
            return;
        }
        if (DataLength > result.Count - 4)//如果包長 大于實際消息長度 說明包不完整 則保存至下次使用
        {
            Tmp_byteArr = result.ToArray();
            return;
        }
        if (DataLength < result.Count - 4)//如果包長 小于實際消息長度 說明存在了粘包 要把多余的內容遞歸使用
        {
            MsgBody msg = new MsgBody(result.Take(DataLength).ToArray());
            //處理消息 
            HandleRecMsg?.Invoke(msg, this, _server);
            ParMsg(result.Skip(DataLength).ToArray());
        }

    }

    /// <summary>
    /// 發(fā)送數(shù)據(jù)
    /// </summary>
    /// <param name="bytes">數(shù)據(jù)字節(jié)</param>
    public void Send(byte[] bytes)
    {
        try
        {
            _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult =>
            {
                try
                {
                    int length = _socket.EndSend(asyncResult);
                    HandleSendMsg?.Invoke(bytes, this, _server);
                }
                catch (Exception ex)
                {
                    HandleException?.Invoke(ex);
                }
            }, null);
        }
        catch (Exception ex)
        {
            HandleException?.Invoke(ex);
        }
    }

    /// <summary>
    /// 發(fā)送字符串(默認使用UTF-8編碼)
    /// </summary>
    /// <param name="msgStr">字符串</param>
    public void Send(string msgStr)
    {
        Send(Encoding.UTF8.GetBytes(msgStr));
    }

    /// <summary>
    /// 發(fā)送字符串(使用自定義編碼)
    /// </summary>
    /// <param name="msgStr">字符串消息</param>
    /// <param name="encoding">使用的編碼</param>
    public void Send(string msgStr, Encoding encoding)
    {
        Send(encoding.GetBytes(msgStr));
    }

    /// <summary>
    /// 傳入自定義屬性
    /// </summary>
    public object Property { get; set; }

    /// <summary>
    /// 關閉當前連接
    /// </summary>
    public void Close()
    {
        try
        {
            _isRec = false;
            _socket.Disconnect(false);
            _server.ClientList.Remove(this);
            HandleClientClose?.Invoke(this, _server);
            _socket.Close();
            _socket.Dispose();
            GC.Collect();
        }
        catch (Exception ex)
        {
            HandleException?.Invoke(ex);
        }
    }

    #endregion

    #region 事件處理

    /// <summary>
    /// 客戶端連接接受新的消息后調用
    /// </summary>
    public Action<MsgBody, SocketConnection, SocketServer> HandleRecMsg { get; set; }

    /// <summary>
    /// 客戶端連接發(fā)送消息后回調
    /// </summary>
    public Action<byte[], SocketConnection, SocketServer> HandleSendMsg { get; set; }

    /// <summary>
    /// 客戶端連接關閉后回調
    /// </summary>
    public Action<SocketConnection, SocketServer> HandleClientClose { get; set; }

    /// <summary>
    /// 異常處理程序
    /// </summary>
    public Action<Exception> HandleException { get; set; }

    #endregion
}


/// <summary>
/// Socket服務端
/// </summary>
public class SocketServer
{
    #region 構造函數(shù)

    /// <summary>
    /// 構造函數(shù)
    /// </summary>
    /// <param name="ip">監(jiān)聽的IP地址</param>
    /// <param name="port">監(jiān)聽的端口</param>
    public SocketServer(string ip, int port)
    {
        _ip = ip;
        _port = port;
    }

    /// <summary>
    /// 構造函數(shù),監(jiān)聽IP地址默認為本機0.0.0.0
    /// </summary>
    /// <param name="port">監(jiān)聽的端口</param>
    public SocketServer(int port)
    {
        _ip = "0.0.0.0";
        _port = port;
    }

    #endregion

    #region 內部成員

    private Socket _socket = null;
    private string _ip = "";
    private int _port = 0;
    private bool _isListen = true;
    private void StartListen()
    {
        try
        {
            _socket.BeginAccept(asyncResult =>
            {
                try
                {
                    Socket newSocket = _socket.EndAccept(asyncResult);

                    //馬上進行下一輪監(jiān)聽,增加吞吐量
                    if (_isListen)
                        StartListen();

                    SocketConnection newClient = new SocketConnection(newSocket, this)
                    {
                        HandleRecMsg = HandleRecMsg == null ? null : new Action<MsgBody, SocketConnection, SocketServer>(HandleRecMsg),
                        HandleClientClose = HandleClientClose == null ? null : new Action<SocketConnection, SocketServer>(HandleClientClose),
                        HandleSendMsg = HandleSendMsg == null ? null : new Action<byte[], SocketConnection, SocketServer>(HandleSendMsg),
                        HandleException = HandleException == null ? null : new Action<Exception>(HandleException)
                    };

                    newClient.StartRecMsg();
                    ClientList.AddLast(newClient);

                    HandleNewClientConnected?.Invoke(this, newClient);
                }
                catch (Exception ex)
                {
                    HandleException?.Invoke(ex);
                }
            }, null);
        }
        catch (Exception ex)
        {
            HandleException?.Invoke(ex);
        }
    }

    #endregion

    #region 外部接口

    /// <summary>
    /// 開始服務蕾久,監(jiān)聽客戶端
    /// </summary>
    public void StartServer()
    {
        try
        {
            //實例化套接字(ip4尋址協(xié)議摧玫,流式傳輸弱判,TCP協(xié)議)
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //創(chuàng)建ip對象
            IPAddress address = IPAddress.Parse(_ip);
            //創(chuàng)建網絡節(jié)點對象包含ip和port
            IPEndPoint endpoint = new IPEndPoint(address, _port);
            //將 監(jiān)聽套接字綁定到 對應的IP和端口
            _socket.Bind(endpoint);
            //設置監(jiān)聽隊列長度為Int32最大值(同時能夠處理連接請求數(shù)量)
            _socket.Listen(int.MaxValue);
            //開始監(jiān)聽客戶端
            StartListen();
            HandleServerStarted?.Invoke(this);
        }
        catch (Exception ex)
        {
            HandleException?.Invoke(ex);
        }
    }

    /// <summary>
    /// 所有連接的客戶端列表
    /// </summary>
    public LinkedList<SocketConnection> ClientList { get; set; } = new LinkedList<SocketConnection>();

    /// <summary>
    /// 關閉指定客戶端連接
    /// </summary>
    /// <param name="theClient">指定的客戶端連接</param>
    public void CloseClient(SocketConnection theClient)
    {
        theClient.Close();
    }

    #endregion

    #region 公共事件

    /// <summary>
    /// 異常處理程序
    /// </summary>
    public Action<Exception> HandleException { get; set; }

    #endregion

    #region 服務端事件

    /// <summary>
    /// 服務啟動后執(zhí)行
    /// </summary>
    public Action<SocketServer> HandleServerStarted { get; set; }

    /// <summary>
    /// 當新客戶端連接后執(zhí)行
    /// </summary>
    public Action<SocketServer, SocketConnection> HandleNewClientConnected { get; set; }

    /// <summary>
    /// 服務端關閉客戶端后執(zhí)行
    /// </summary>
    public Action<SocketServer, SocketConnection> HandleCloseClient { get; set; }

    #endregion

    #region 客戶端連接事件

    /// <summary>
    /// 客戶端連接接受新的消息后調用
    /// </summary>
    public Action<MsgBody, SocketConnection, SocketServer> HandleRecMsg { get; set; }

    /// <summary>
    /// 客戶端連接發(fā)送消息后回調
    /// </summary>
    public Action<byte[], SocketConnection, SocketServer> HandleSendMsg { get; set; }

    /// <summary>
    /// 客戶端連接關閉后回調
    /// </summary>
    public Action<SocketConnection, SocketServer> HandleClientClose { get; set; }

    #endregion
}

public class MsgBody
{
public MsgBody()
{

    }
    public MsgBody(string str)
    {
        this.BodyData = str;
    }
    public MsgBody(byte[] b)
    {
        this.Source = b;
        SetValues();
    }

    /// <summary>
    /// 包長
    /// </summary>
    public int DataLength;
    public byte[] DataLength_Byte;

    /// <summary>
    /// 包長
    /// </summary>
    public int DataLength_2;
    public byte[] DataLength_2_Byte;

    /// <summary>
    /// 命令字 用來標記消息類型
    /// </summary>
    public int Cmd;
    public byte[] Cmd_Byte;

    /// <summary>
    /// 發(fā)生的文本內容
    /// </summary>
    public string BodyData;
    public byte[] BodyData_Byte;

    public byte End = 0;

    public byte[] Source;

    /// <summary>
    /// 數(shù)據(jù)組合為byte數(shù)組
    /// </summary>
    /// <returns></returns>
    public byte[] ToByteArray()
    {
        BodyData_Byte = Encoding.UTF8.GetBytes(BodyData);
        Cmd_Byte = BitConverter.GetBytes(Cmd);
        DataLength = DataLength_2 = BodyData_Byte.Length + Cmd_Byte.Length + 4 + 1;
        DataLength_Byte = BitConverter.GetBytes(DataLength);
        DataLength_2_Byte = BitConverter.GetBytes(DataLength_2);

        byte[] result = new byte[DataLength + 4];
        Array.Copy(DataLength_Byte, 0, result, 0, 4);
        Array.Copy(DataLength_2_Byte, 0, result, 4, 4);
        Array.Copy(Cmd_Byte, 0, result, 8, 4);
        Array.Copy(BodyData_Byte, 0, result, 12, BodyData_Byte.Length);
        Source = new byte[result.Length];
        Array.Copy(result, 0, Source, 0, result.Length);
        return result;
    }

    /// <summary>
    /// 數(shù)據(jù)解析
    /// </summary>
    public void SetValues()
    {
        try
        {
            DataLength_Byte = Common.SubByte(Source, 0, 4);
            DataLength_2_Byte = Common.SubByte(Source, 4, 4);
            Cmd_Byte = Common.SubByte(Source, 8, 4);
            BodyData_Byte = Common.SubByte(Source, 12, Source.Length - 13);
            End = Common.SubByte(Source, Source.Length - 2, 1)[0];

            DataLength = BitConverter.ToInt32(DataLength_Byte, 0);
            DataLength_2 = BitConverter.ToInt32(DataLength_2_Byte, 0);
            Cmd = BitConverter.ToInt32(Cmd_Byte, 0);
            BodyData = Encoding.UTF8.GetString(BodyData_Byte);
        }
        catch (Exception ex)
        {

            throw;
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市勘伺,隨后出現(xiàn)的幾起案子跪腹,更是在濱河造成了極大的恐慌,老刑警劉巖飞醉,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冲茸,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機轴术,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門蹲盘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人膳音,你說我怎么就攤上這事召衔。” “怎么了祭陷?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵苍凛,是天一觀的道長。 經常有香客問我兵志,道長醇蝴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任想罕,我火速辦了婚禮悠栓,結果婚禮上,老公的妹妹穿的比我還像新娘按价。我一直安慰自己惭适,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布楼镐。 她就那樣靜靜地躺著癞志,像睡著了一般。 火紅的嫁衣襯著肌膚如雪框产。 梳的紋絲不亂的頭發(fā)上凄杯,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音秉宿,去河邊找鬼戒突。 笑死,一個胖子當著我的面吹牛描睦,可吹牛的內容都是我干的膊存。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼酌摇,長吁一口氣:“原來是場噩夢啊……” “哼膝舅!你這毒婦竟也來了嗡载?” 一聲冷哼從身側響起窑多,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洼滚,沒想到半個月后埂息,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年千康,在試婚紗的時候發(fā)現(xiàn)自己被綠了享幽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡拾弃,死狀恐怖值桩,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情豪椿,我是刑警寧澤奔坟,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站搭盾,受9級特大地震影響咳秉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鸯隅,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一澜建、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝌以,春花似錦炕舵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碍彭,卻和暖如春晤硕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庇忌。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工舞箍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皆疹。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓疏橄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親略就。 傳聞我的和親對象是個殘疾皇子捎迫,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內容