unity TCP协议收发数据:封包、拆包

通信原理

unity 支持自定义的Socket:在使用的时候需要关闭防火墙
动态查找IP:因为服务端的Ip不是固定的,所以服务器用UDP广播一段特殊的消息,客服端接受并匹配上那么链接成功。
发送消息:
1 客服端: 封装消息体。 封装的原因:防止沾包
封包=标头+消息内容
2.服务端:解包

服务端
/**
 *  2019.9.20
 *  定义Socket属性
 * */
using System;
using System.Text;
using System.Net.Sockets;
using UnityEngine;

namespace Item_A
{
    class Conn
    {
        public const int buff_size = 1024;
        public Socket socket;
        public bool IsUse = false;
        public Byte[] readBuff = new byte[buff_size];
        public int byteCount = 0;

        //解析 字节流
        StringBuilder receiveStr;
        int receiveCount;
        private string type;

        //help
        public static bool PlayAudioState=false;

        public void Init(Socket socket)
        {
            this.socket = socket;
            IsUse = true;
            byteCount = 0;
        }

        /// <summary>
        /// 缓冲字节数
        /// </summary>
        public int Buffemain()
        {
            return buff_size - byteCount;
        }

        /// <summary>
        /// 得到服务器端口号和IP
        /// </summary>
        public string GetAdress()
        {
            if (!IsUse)
                return "无法获取地址";
            return socket.RemoteEndPoint.ToString();
        }

        /// <summary>
        /// 关闭服务器
        /// </summary>
        public void Colse()
        {
            if (!IsUse)
                return;
            Debug.Log("客服端与服务端断开");
            IsUse = false;
            socket.Close();
        }

        private int num = 4;
        private int index = 5;

        /// <summary>
        /// 解析数据 ,count 新读取到的数据长度
        /// </summary>
        public StringBuilder ReadMessage(int count)
        {
            byteCount = count;
            //用while表示缓存区,可能有多条数据
            while (true)
            {
                //缓存区小于4个字节,表示连表头都无法解析
                if (byteCount <= num) return null;

                //读取四个字节数据,代表这条数据的内容长度(不包括表头的4个数据)
                receiveCount = BitConverter.ToInt32(readBuff, 0);
                //int receiveCount = BitConverter(readBuff, 0);

                //缓存区中的数据,不够解析一条完整的数据
                if (byteCount - num < receiveCount) return null;

                //2、解析数据
                //从除去表头4个字节开始解析内容,解析的数据长度为(表头数据表示的长度)
                receiveStr = new StringBuilder(Encoding.UTF8.GetString(readBuff, index, receiveCount));
                type = Encoding.UTF8.GetString(readBuff, 4, 1);

                switch (type)
                {
                    case "0":
                        Debug.LogError("数字" + "  消息内容:" + receiveStr);
                        break;
                    case "1":
                        Debug.LogError("汉字" + "  消息内容:" + receiveStr);
                        PlayAudioState = true;
                        break;
                    case "2":
                        Debug.LogError("心跳" + "  消息内容:" + receiveStr);
                        break;
                    case "3":
                        Debug.LogError("不播放音效" + "  消息内容:" + receiveStr);
                        break;
                }

                //把剩余的数据Copy到缓存区头部位置
                Array.Copy(readBuff, index + receiveCount, readBuff, 0, byteCount - index - receiveCount);
                byteCount = byteCount - index - receiveCount;
                return receiveStr;
            }
        }
    }
}
/*
 *2019.9.20 
 * 定义服务器
 * */

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
using System.Text.RegularExpressions;

namespace Item_A
{
    class Server
    {
        public Socket socketfd;
        public int maxConnect = 50;
        public Conn[] conns;
        public static string str;
        public static float num;

        private string message;
        /// <summary>
        /// 连接池
        /// </summary>
        /// <returns></returns>
        public int NewIndex()
        {
            if (conns == null)
            {
                return -1;
            }

            for (int i = 0; i < conns.Length; i++)
            {
                if (conns == null)
                {
                    conns[i] = new Conn();
                    return i;
                }
                else if (conns[i].IsUse == false)
                {
                    return i;
                }
            }
            return -1;
        }

        public void SeverStart(string Host, int port)
        {
            conns = new Conn[maxConnect];
            for (int i = 0; i < conns.Length; i++)
            {
                conns[i] = new Conn();
            }

            //Socket
            socketfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAdress = IPAddress.Parse(Host);             //本机Ip地址 
            IPEndPoint ipEp = new IPEndPoint(ipAdress, port);       //端口号

            //绑定Ip
            socketfd.Bind(ipEp);
            //开始监听
            socketfd.Listen(maxConnect);
            //接收客户端的消息
            socketfd.BeginAccept(AcceptCb, null);
            Debug.Log("开始监听");
        }

        //回调(循环的接受)
        private void AcceptCb(IAsyncResult ar)
        {
            try
            {
                Socket socketA = socketfd.EndAccept(ar);
                int index = NewIndex();

                if (index < 0)
                {
                    Debug.Log("连接已经满了");
                }
                else
                {
                    Conn conn = conns[index];
                    conn.Init(socketA);
                    string adr = conn.GetAdress();
                    Debug.Log("客服端连接:" + adr + "索引:" + index + "个数:" + conn.byteCount);
                    conn.socket.BeginReceive(conn.readBuff, conn.byteCount, conn.Buffemain(), SocketFlags.None, ReceiveCb, conn);
                    socketfd.BeginAccept(AcceptCb, null);//再次调用      
                }
            }
            catch (Exception e)
            {
                Debug.Log("回调失败" + e);
            }
        }

        Conn conn;
        //回调
        private void ReceiveCb(IAsyncResult ar)
        {
             conn = (Conn)ar.AsyncState;
            try
            {
                int count = conn.socket.EndReceive(ar);
                //客服端与服务端断开连接
                if (count <= 0)
                {
                    Debug.Log(conn.GetAdress() + "与服务器断开连接");
                    conn.Colse();
                    return;
                }

                 message = conn.ReadMessage(count).ToString();       // Encoding.UTF8.GetString(conn.readBuff, 0, count);
                if (message == "心跳")
                {
                    SendCb(conn.socket, message);
                }
                else
                {
                    str = message;
                }
                num = conn.GetconnNum;
                conn.socket.BeginReceive(conn.readBuff, conn.byteCount, conn.Buffemain(), SocketFlags.None, ReceiveCb, conn);
            }
            catch (Exception e)
            {
                Debug.Log("收到" + conn.GetAdress() + "断开连接" + e.Message);
                conn.Colse();
            }
        }

        public void SendCb(Socket socket, string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return;
            }
            byte[] bytes = Encoding.UTF8.GetBytes(value);
            socket.Send(bytes);
        }
        public void SendCb( string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return;
            }
            byte[] bytes = Encoding.UTF8.GetBytes(value);
            conn.socket.Send(bytes);
        }
    }
/**
 * 2019.9.20
 * 开启服务器
 * */
using Item_A;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

public class StartSever : MonoBehaviour
{
    UdpClient UdpSend;
    const string specialText = "123!!@@##$$%%-XinyuanInteractiveDisplaySystem";
    Thread serverThread;
    public static string str = "";
    public Text text;
    Server server;
    void Start()
    {
        server = new Server();
        BroadcastMessage();

        //server.Start("192.168.1.169", 1533);

        server.SeverStart(IPAddress.Any.ToString(), 1533);
    }

    private void Update()
    {
        if (text != null)
        {
            text.text = str;
        }
    }
    public void BroadcastMessage()
    {
        serverThread = new Thread(() =>
      {
          UdpSend = new UdpClient();
          while (true)
          {
              Thread.Sleep(1000);
              byte[] buf = Encoding.Unicode.GetBytes(specialText);
              UdpSend.Send(buf, buf.Length, new IPEndPoint(IPAddress.Broadcast, 1533));
              print("广播中....");
          }
      });
        serverThread.IsBackground = true;
        serverThread.Start();
    }

    public void Complate()
    {
        //if (server != null)
        //{
        //    server.SendCb("完成");
        //}
    }

    void OnApplicationQuit()
    {
        serverThread.Abort();
        server.SendCb("结束");
    }

}
/**
 *开启客户端 
 */
using UnityEngine;
using System.Text;
using UnityEngine.UI;
using System.Net;
using System.Net.Sockets;
using System;
using System.Threading;
using System.IO;

public class ConnnectA : MonoBehaviour
{
    public string serverstr;
    public GameObject WaitePlane;
    Socket socket;
    const int buff_size = 1024;
    byte[] readbuff = new byte[buff_size];
    public static ConnnectA ConnnectAInstance;
    public Text index;

    //private
    private Thread clientThread;
    private UdpClient UdpListen;
    private int port = 1533;
    private bool clientIsRun = true;

    const string specialText = "123!!@@##$$%%-XinyuanInteractiveDisplaySystem";

    public static string connectStr = "";

    //心跳属性
    public bool isConnect = false;
    public static float heartbeatTime = 0f;
    public Thread heartbeatThread;
    private void Start()
    {
        ConnnectAInstance = this;
        heartbeatTime = 0f;
        Connection();
    }

    //实时更新数据
    private void Update()
    {
        if (!clientIsRun && isConnect)
        {
            if (heartbeatTime > 10f)
            {
                heartbeatTime = 0f;
                Reconnect();
            }
            else
            {
                heartbeatTime += Time.deltaTime;
            }
        }
        if (connectStr == "完成连接")
        {
            SetWaitePlaneState(false);
        }
        if (connectStr == "连接断开")
        {
            SetWaitePlaneState(true);
        }
    }

    private void ReceiveCb(IAsyncResult ar)
    {
        int count = socket.EndReceive(ar);
        try
        {
            string str = Encoding.UTF8.GetString(readbuff, 0, count);

            if (str == "心跳")
            {
                heartbeatTime = 0f;
            }
            if (str == "结束")
            {
                connectStr = "连接断开";
            }

            if (serverstr.Length > 300)
            {
                serverstr = "";
            }
            serverstr += str + "\n";

            //继续接受数据
            socket.BeginReceive(readbuff, 0, buff_size, SocketFlags.None, ReceiveCb, null);
        }
        catch (Exception e)
        {
            connectStr += "断开连接";
            print("客服端与服务端断开" + e.Message);
            socket.Close();
        }
    }

    public void Send(string value,int type)
    {
        if (socket == null)
        {
            print("未连接:" + value);
            return;
        }
        try
        {
            socket.Send(SendMsg(value,type));
            print("发送消息:" + value);
        }
        catch (Exception e)
        {
            print("发送失败" + e.Message);
        }
    }

    //接受消息
    public void Connection()
    {
        SetWaitePlaneState(true);
        clientThread = new Thread(() =>
        {
            while (clientIsRun)
            {
                connectStr = "接受UDP";
                UdpListen = new UdpClient(new IPEndPoint(IPAddress.Any, port));
                IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port);
                byte[] bufRev = UdpListen.Receive(ref endpoint);
                string msg = Encoding.Unicode.GetString(bufRev, 0, bufRev.Length);
                if (msg.Contains(specialText))
                {
                    connectStr = "接受UDP完成";
                    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    socket.Connect(endpoint.Address.ToString(), port);
                    //心跳
                    isConnect = true;
                    heartbeatTime = 0f;
                    heartbeatThread = new Thread(() =>
                    {
                        while (isConnect)
                        {
                            Thread.Sleep(5000);
                            Send("心跳",2);
                        }
                    });
                    heartbeatThread.Start();
                    socket.BeginReceive(readbuff, 0, buff_size, SocketFlags.None, ReceiveCb, null);
                    connectStr = "完成连接";
                    Debug.Log("连接成功");
                    CloseClientBroadcast();
                    return;
                }
            }
        });
        //CloseClientBroadcast();
        clientThread.Start();
    }

    //关闭广播
    public void CloseClientBroadcast()
    {
        clientIsRun = false;
        if (UdpListen != null) UdpListen.Close();
        if (clientThread != null && clientThread.IsAlive) clientThread.Abort();
    }

    //标头
    private byte[] HeadBytes;
    private int length;
    private byte[] headerBytes;
    //消息内容
    private byte[] bodyBytes;
    private byte[] typeBytes;
    private byte[] tempBytes;

    private byte[] _bytes;

    /// <summary>
    /// 构造发送数据
    /// </summary>
    /// <param name="msg"></param>
    /// <param name="type">0是代表数字类型 1是代表string类型 2是代表"心跳" 3代表不播放音效</param>
    /// <returns></returns>
    public byte[] SendMsg(string msg, int type)
    {
        HeadBytes = Encoding.UTF8.GetBytes(msg);
        length = HeadBytes.Length;
        //构造表头数据,固定4个字节的长度,表示内容的长度
        headerBytes = BitConverter.GetBytes(length);
        //构造内容
        bodyBytes = Encoding.UTF8.GetBytes(msg);
        typeBytes = Encoding.UTF8.GetBytes(type.ToString());

        using (MemoryStream memoryStream = new MemoryStream()) //创建内存流
        {
            BinaryWriter binaryWriter = new BinaryWriter(memoryStream); //以二进制写入器往这个流里写内容
            binaryWriter.Write(headerBytes);
            binaryWriter.Write(typeBytes);
            binaryWriter.Write(bodyBytes);
            _bytes = memoryStream.ToArray(); //将流内容写入自定义字节数组
            binaryWriter.Close(); //关闭写入器释放资源
        }
        return _bytes;
    }

    public static byte[] ToArray(byte[] firstBytes, int firstIndex, int firstLength, byte[] secondBytes, int secondIndex, int secondLength)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            BinaryWriter bw = new BinaryWriter(ms);
            bw.Write(firstBytes, firstIndex, firstLength);
            bw.Write(secondBytes, secondIndex, secondLength);
            bw.Close();
            bw.Dispose();
            return ms.ToArray();
        }
    }

    /// <summary>
    /// 初始重连
    /// </summary>
    public void Reconnect()
    {
        clientIsRun = true;
        isConnect = false;
        heartbeatTime = 0f;
        print("重连服务器");
       // SetWaitePlaneState(false);
        if (socket != null)
        {
            socket.Close();
        }
        if (UdpListen != null) UdpListen.Close();
        if (clientThread != null && clientThread.IsAlive) clientThread.Abort();
        if (heartbeatThread != null && heartbeatThread.IsAlive) heartbeatThread.Abort();
        Connection();
    }

    //string GetAddressIP()
    //{
    //    / 获取服务端
    //    string AddressIP = string.Empty;
    //    foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
    //    {
    //        if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
    //        {
    //            AddressIP = _IPAddress.ToString();
    //        }
    //    }
    //    return AddressIP;
    //}

    /// <summary>
    /// 等待面板状态
    /// </summary>
    /// <param name="state"></param>
    public void SetWaitePlaneState(bool state)
    {
        if(WaitePlane.activeSelf!= state)
        WaitePlane.SetActive(state);
    }

    void OnApplicationQuit()
    {
        CloseClientBroadcast();
    }

}

效果图
在这里插入图片描述

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Unity中使用TCP协议传输数据可以通过使用C#中的Socket类来实现。下面是一个简单的示例代码,展示了如何在Unity中使用TCP协议传输数据: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; public class TCPClient : MonoBehaviour { private TcpClient client; private NetworkStream stream; private byte[] receiveBuffer; private void Start() { ConnectToServer("127.0.0.1", 8888); // 连接到服务器的IP和端口 } private void ConnectToServer(string serverIP, int serverPort) { try { client = new TcpClient(); client.Connect(serverIP, serverPort); stream = client.GetStream(); receiveBuffer = new byte[1024]; // 启动异步接收数据 stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, OnReceiveData, null); } catch (Exception e) { Debug.Log("连接服务器失败:" + e.Message); } } private void OnReceiveData(IAsyncResult ar) { try { int bytesRead = stream.EndRead(ar); if (bytesRead <= 0) { Debug.Log("与服务器断开连接"); return; } string receivedMessage = Encoding.ASCII.GetString(receiveBuffer, 0, bytesRead); Debug.Log("收到服务器消息:" + receivedMessage); // 继续异步接收数据 stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, OnReceiveData, null); } catch (Exception e) { Debug.Log("接收数据时发生错误:" + e.Message); } } private void SendMessageToServer(string message) { try { byte[] data = Encoding.ASCII.GetBytes(message); stream.Write(data, 0, data.Length); Debug.Log("发送消息到服务器:" + message); } catch (Exception e) { Debug.Log("发送消息时发生错误:" + e.Message); } } private void OnDestroy() { if (stream != null) stream.Close(); if (client != null) client.Close(); } // 示例使用的按钮点击事件 public void SendButtonOnClick() { SendMessageToServer("Hello, Server!"); } } ``` 以上示例代码是一个简单的TCP客户端,它通过Socket连接到指定的服务器IP和端口,然后可以发送和接收数据。你可以根据自己的需求进行修改和扩展。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值