Unity 使用TCP做為客戶端,並進入粘包處理
阿新 • • 發佈:2019-02-05
客戶端:
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; } }