1. 程式人生 > >Unity 使用TCP做為客戶端,並進入粘包處理

Unity 使用TCP做為客戶端,並進入粘包處理

客戶端:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;

/// <summary>
/// 連線網路,處理網路異常
/// </summary>
public class NetSocket : MonoBehaviour
{
    /// <summary> 連線異常 </summary>
    public delegate void CallBackNormal(bool sucess, SocketError socketError, string exception);

    /// <summary> 接收異常 </summary>
    public delegate void RecvUnSucessUnKnow(
        bool sucess, SocketError socketError, string exception, byte[] bytes, string msg);


    public event CallBackNormal CallBackConnect;
    public event CallBackNormal CallBackSend;
    public event CallBackNormal CallBackDisConnect;
    public event RecvUnSucessUnKnow CallBackRecv;

    public enum SocketError
    {
        /// <summary> 連線成功 </summary>
        Sucess = 0,

        /// <summary> 連線超時 </summary>
        TimeOut,

        /// <summary> 套接字為空 </summary>
        SocketNull,

        /// <summary> 連線失敗 </summary>
        SocketUnConnect,

        ConnectSucess,

        /// <summary> 連線錯誤,未知錯誤 </summary>
        ConnectUnSucessUnKnow,

        /// <summary> 連線錯誤 </summary>
        ConnectError,

        SendSucess,

        /// <summary> 傳送失敗 </summary>
        SendUnSucessUnKnow,

        /// <summary> 接收失敗 </summary>
        RecvUnSucessUnKnow,

        DisConnectSucess,
        /// <summary> 斷開連線 </summary>
        DisConnectUnknow,
    }

    private Socket _clientSocket;
    private SocketError _socketError;
    private string _addressIp;
    private int _port;

    private SocketBuffer _recvBuffer;
    private byte[] _recvBytes;

    public NetSocket()
    {
        _recvBuffer = new SocketBuffer(6, CallBackRecvOver);
        _recvBytes = new byte[1024];
    }



    #region 連線

    public void AsyncConnect(string ip, int port, CallBackNormal connectBack, RecvUnSucessUnKnow recvBack)
    {
        _socketError = SocketError.Sucess;

        CallBackConnect = connectBack;
        CallBackRecv = recvBack;

        //是否連線過
        if (_clientSocket != null && _clientSocket.Connected)
        {
            if (CallBackConnect != null)
                CallBackConnect(false, SocketError.ConnectError, "重複連線");
        }
        else if (_clientSocket == null || !_clientSocket.Connected)
        {
            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPAddress ipAddress = IPAddress.Parse(ip);
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);

            IAsyncResult asyncResult = _clientSocket.BeginConnect(ipEndPoint, CallbackConnect, _clientSocket);

            if (!TimeOut(asyncResult))
            {
                if (CallBackConnect != null)
                    CallBackConnect(false, _socketError, "連線超時");
            }
        }
    }

    private void CallbackConnect(IAsyncResult ar)
    {
        try
        {
            _clientSocket.EndConnect(ar);

            if (!_clientSocket.Connected)
            {
                _socketError = SocketError.ConnectUnSucessUnKnow;
                if (CallBackConnect != null)
                    CallBackConnect(false, _socketError, "連線失敗");
            }
            else
            {
                _socketError = SocketError.ConnectSucess;
                if (CallBackConnect != null)
                    CallBackConnect(true, _socketError, "連線成功");
            }
        }
        catch (Exception e)
        {
            if (CallBackConnect != null)
                CallBackConnect(false, _socketError, e.ToString());
        }
    }

    #endregion

    #region 資料接收

    private void AsyncRecive()
    {
        if (_clientSocket != null && _clientSocket.Connected)
        {
            _clientSocket.BeginReceive(_recvBytes, 0, _recvBytes.Length, SocketFlags.None, CallbackRecive, _clientSocket);
        }
    }

    private void CallbackRecive(IAsyncResult ar)
    {
        try
        {
            if (!_clientSocket.Connected && CallBackRecv != null)
            {
                CallBackRecv(false, SocketError.RecvUnSucessUnKnow, "連線斷開", null, "");
                return;
            }

            int lenth = _clientSocket.EndReceive(ar);
            if (lenth <= 0) return;

            _recvBuffer.RecvByte(_recvBytes, lenth);
        }
        catch (Exception e)
        {
            if (CallBackRecv != null)
                CallBackRecv(false, SocketError.RecvUnSucessUnKnow, e.ToString(), null, "");
        }

        AsyncRecive();
    }

    private void CallBackRecvOver(byte[] addData)
    {
        if (CallBackRecv != null)
            CallBackRecv(true, SocketError.Sucess, "", null, "");
    }

    #endregion

    #region 資料傳送

    private void AsyncSend(byte[] sendBytes, CallBackNormal callBackNormal)
    {
        _socketError = SocketError.Sucess;
        CallBackSend = callBackNormal;

        if (_clientSocket == null && CallBackSend != null)
        {
            CallBackSend(false, SocketError.SendUnSucessUnKnow, "套接字為空");
        }
        else if (!_clientSocket.Connected && CallBackSend != null)
        {
            CallBackSend(false, SocketError.SendUnSucessUnKnow, "連線斷開");
        }
        else
        {
            IAsyncResult asyncResult = _clientSocket.BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None,
                CallbackSend, _clientSocket);

            if (!TimeOut(asyncResult))
            {
                if (CallBackSend != null)
                    CallBackSend(false, SocketError.SendUnSucessUnKnow, "連線超時");
            }
        }
    }

    private void CallbackSend(IAsyncResult ar)
    {
        try
        {
            if (!_clientSocket.Connected && CallBackSend != null)
            {
                CallBackSend(false, SocketError.SendUnSucessUnKnow, "");
                return;
            }

            int lenth = _clientSocket.EndSend(ar);
            if (lenth <= 0) return;

            if (CallBackSend != null)
                CallBackSend(true, SocketError.SendSucess, "傳送完成");
        }
        catch (Exception e)
        {
            if (CallBackSend != null)
                CallBackSend(false, SocketError.RecvUnSucessUnKnow, e.ToString());
        }
    }

    #endregion

    #region 連線超時

    /// <summary>
    /// 連線超時
    /// </summary>
    /// <param name="ar"></param>
    /// <returns>false: 超時</returns>
    private bool TimeOut(IAsyncResult ar)
    {
        int i = 0;
        while (ar.IsCompleted == false)
        {
            i++;
            if (i > 20)
            {
                _socketError = SocketError.TimeOut;
                return false;
            }
            Thread.Sleep(100);
        }
        return true;
    }

    #endregion

    #region 斷開連線

    private void AsyncDisconnect(CallBackNormal disconnect)
    {
        try
        {
            CallBackDisConnect = disconnect;

            if (_clientSocket == null && CallBackDisConnect != null)
            {
                CallBackDisConnect(false, SocketError.DisConnectUnknow, "套接字為空");
            }
            else if (!_clientSocket.Connected && CallBackDisConnect != null)
            {
                CallBackDisConnect(false, SocketError.DisConnectUnknow, "斷開連線");
            }
            else
            {
                _clientSocket.BeginDisconnect(false, CallbackDisconnect, _clientSocket);
            }
        }
        catch (Exception e)
        {
            if (CallBackDisConnect != null)
                CallBackDisConnect(false, SocketError.DisConnectUnknow, e.ToString());
        }
    }

    private void CallbackDisconnect(IAsyncResult ar)
    {
        try
        {
            if (!_clientSocket.Connected && CallBackDisConnect != null)
            {
                CallBackDisConnect(false, SocketError.DisConnectUnknow, "");
                return;
            }

            _clientSocket.EndDisconnect(ar);
            _clientSocket.Close();
            _clientSocket = null;

            if (CallBackDisConnect != null)
                CallBackDisConnect(true, SocketError.DisConnectSucess, "斷開連線");
        }
        catch (Exception e)
        {
            if (CallBackDisConnect != null)
                CallBackDisConnect(false, SocketError.DisConnectUnknow, e.ToString());
        }
    }

    #endregion
}

粘包處理:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using UnityEngine;

/// <summary>
/// 粘包,分包
/// </summary>
public class SocketBuffer
{
    /// <summary> 包頭 </summary>
    private byte[] _headBytes;

    /// <summary> 資料 </summary>
    private byte[] _allRecvBytes;

    /// <summary> 包頭長度 </summary>
    private byte _headLenth;

    /// <summary> 當前接收多少資料 </summary>
    private int _curRecvLenth;

    /// <summary> 總資料長度 </summary>
    private int _allDataLenth;

    public delegate void CallBackRecvOver(byte[] addData);

    private CallBackRecvOver _callBackRecvOver;

    public SocketBuffer(byte headLenth , CallBackRecvOver callBackRecvOver)
    {
        _headLenth = headLenth;
        _headBytes = new byte[_headLenth];

        _callBackRecvOver = callBackRecvOver;
    }

    /// <summary>
    /// 處理接收來的資料
    /// </summary>
    /// <param name="recvBytes">接收到的</param>
    /// <param name="realLenth">這個資料真正的長度</param>
    public void RecvByte(byte[] recvBytes, int realLenth)
    {
        if (realLenth <= 0) return;

        //當前接收的資料小於頭長度
        if (_curRecvLenth < _headLenth)
        {
            RecvHead(recvBytes, realLenth);
        }
        else
        {
            //接收總長度
            int lenth = _curRecvLenth + realLenth;

            //正好一條資料
            if (lenth == _allDataLenth)
            {
                RecvOneAll(recvBytes, realLenth);
            }
            else if (lenth > _allDataLenth) //接收的資料比這個訊息長
            {
                RecvLarger(recvBytes, realLenth);
            }
            else
            {
                RecvSmall(recvBytes, realLenth);
            }
        }
    }

    /// <summary>
    /// 接收的資料比這個訊息長
    /// </summary>
    private void RecvLarger(byte[] recvBytes, int realLenth)
    {
        int lenth = _allDataLenth - _curRecvLenth;

        Buffer.BlockCopy(recvBytes, 0, _allRecvBytes, _curRecvLenth, lenth);

        _curRecvLenth += lenth;

        RecvOneMsgOver();

        //接收下一條訊息
        int remainLenth = realLenth - lenth;

        byte[] remainBytes = new byte[remainLenth];

        Buffer.BlockCopy(recvBytes, lenth, remainBytes, 0, remainLenth);

        RecvByte(remainBytes, remainLenth);
    }

    /// <summary>
    /// 接收的資料比這個訊息短
    /// </summary>
    private void RecvSmall(byte[] recvBytes, int realLenth)
    {
        Buffer.BlockCopy(recvBytes, 0, _allRecvBytes, _curRecvLenth, realLenth);    //拷來後繼續接收

        _curRecvLenth += realLenth;
    }

    /// <summary>
    /// 剛好一條完整的資料
    /// </summary>
    private void RecvOneAll(byte[] recvBytes, int realLenth)
    {
        Buffer.BlockCopy(recvBytes, 0, _allRecvBytes, _curRecvLenth, realLenth);

        _curRecvLenth += realLenth;

        RecvOneMsgOver();
    }

    /// <summary>
    /// 處理頭部
    /// </summary>
    /// <param name="recvBytes"></param>
    /// <param name="realLenth"></param>
    private void RecvHead(byte[] recvBytes, int realLenth)
    {
        //頭 還差多少資料
        int real = _headLenth - _curRecvLenth;
        //現在還有的 和 已經又接收的  總和
        int lenth = _curRecvLenth + realLenth;

        //總長度小於頭
        if (lenth < _headLenth)
        {
            Buffer.BlockCopy(recvBytes, 0, _headBytes, _curRecvLenth, realLenth);
            _curRecvLenth += realLenth;
        }
        else
        {
            Buffer.BlockCopy(recvBytes, 0, _headBytes, _curRecvLenth, real);
            _curRecvLenth += real;

            _allDataLenth = BitConverter.ToInt32(_headBytes, 0) + _headLenth;
            _allRecvBytes = new byte[_allDataLenth];
            Buffer.BlockCopy(_headBytes, 0, _allRecvBytes, 0, _headBytes.Length);

            int remin = realLenth - real;

            //recvBytes是否還有資料
            if (remin > 0)
            {
                byte[] tmpBytes = new byte[remin];

                //將剩下位元組送入tmpBytes
                Buffer.BlockCopy(recvBytes, real, tmpBytes, 0, remin);

                RecvByte(tmpBytes, remin);
            }
            else
            {
                RecvOneMsgOver();
            }
        }
    }



    /// <summary>
    /// 完成一個數據的接收
    /// </summary>
    private void RecvOneMsgOver()
    {
        if (_callBackRecvOver != null)
        {
            _callBackRecvOver(_allRecvBytes);
        }

        _curRecvLenth = 0;
        _allDataLenth = 0;
        _allRecvBytes = null;
    }

}