Unity3D:使用Socket进行网络数据通信

局域网和广域网的数据通信,在平时做项目的时候经常会用到,之前一直用的是Unity3D自带的NetworkView。NetworkView虽然也可以用但是Unity5.x之后就过时了,而且专业版和个人版都会有连接个数限制,即使是专业版的连接数也是很少,而5.x新的网络通信功能Networking有没有仔细的研究过,所以目前来说比较好用的就是Socket了,下面是我自己根据网上的资料自己整理的代码。

首先是服务端

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

public class Server : MonoBehaviour
{
    public static Server instence = null;

    private Socket serverSocket;
    private Socket clientSocket;
    private Thread clientThread;
    private Thread thread;
    private string ipAddress = "";
    private int port = 9000;

    private string messages = null;

    public delegate void ReceiveMessages(string msg);
    public ReceiveMessages receiveMessages;
    // Use this for initialization
    void Awake()
    {
        if (instence == null)
        {
            instence = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    void Update()
    {
        if (!string.IsNullOrEmpty(messages))
        {
            if (receiveMessages != null)
            {
                receiveMessages.Invoke(messages);
            }
            messages = string.Empty;
        }
    }
    /// <summary>
    /// 开启线程,用来启动服务器
    /// </summary>
    /// <param name="ip"></param>
    public void StartThread(string ip)
    {
        Regex ipReg = new Regex(@"^[0-9]{1,3}(\.[0-9]{1,3}){3}$");
        if (!string.IsNullOrEmpty(ip) && ipReg.IsMatch(ip))
        {
            ipAddress = ip;
            thread = new Thread(new ThreadStart(StartServer));
            thread.Start();
        }
        else
        {
            Debug.LogError("IPAdress is Error ! ! !");
        }
    }
    /// <summary>
    /// 程序退出,结束线程
    /// </summary>
    void OnDestroy()
    {
        StopServer();
    }
    /// <summary>
    /// 开启服务器
    /// </summary>
    void StartServer()
    {
        IPAddress ipAdr = IPAddress.Parse(ipAddress);
        IPEndPoint ipEp = new IPEndPoint(ipAdr, port);
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(ipEp);
        serverSocket.Listen(10);

        while (true)
        {
            try
            {
                clientSocket = serverSocket.Accept();
                Debug.Log("Accept");
                clientThread = new Thread(new ThreadStart(ReceiveData));
                clientThread.Start();

            }
            catch (System.Exception ex)
            {
                print(ex);
                break;
            }
        }
    }
    /// <summary>
    /// 断开服务端
    /// </summary>
    public void StopServer()
    {
        if (serverSocket != null)
        {
            serverSocket.Close();
            serverSocket = null;
        }
        if (thread != null)
        {
            thread.Abort();
            thread = null;
        }
    }
    /// <summary>
    /// 发送消息
    /// </summary>
    public void SendMessageToClient(string message)
    {
        int msgLen = Encoding.UTF8.GetByteCount(message);
        byte[] data = new byte[4 + msgLen];
        //把长度转成字节数组 
        byte[] lenbytes = BitConverter.GetBytes(msgLen);
        //把消息转为字节数组
        byte[] bodybytes = Encoding.UTF8.GetBytes(message);
        //将len添加到bytes数组中
        Array.Copy(lenbytes, data, 4);
        //将实际内容添加到bytes数组中
        Array.Copy(bodybytes, 0, data, 4, msgLen);
        if (serverSocket != null)
        {
            serverSocket.Send(data);
        }
    }
    /// <summary>
    /// 接收客户端数据
    /// </summary>
    void ReceiveData()
    {
        bool keepalive = true;
        Socket s = clientSocket;
        //根据收听到的客户端套接字向客户端发送信息  
        IPEndPoint clientep = (IPEndPoint)s.RemoteEndPoint;
        Debug.Log("IP为" + clientep.Address + "的客户端连接成功");

        while (keepalive)
        {
            //在套接字上接收客户端发送的信息  
            int bufLen = 0;
            byte[] buffer = new byte[1024];
            //try
            //{
            bufLen = s.Receive(buffer);
            Debug.Log(bufLen);
            if (bufLen == 0)
                break;
            if (shengyu_data != null)
            {
                byte[] newdata = new byte[shengyu_data.Length + bufLen];
                Array.Copy(shengyu_data, newdata, shengyu_data.Length);
                Array.Copy(buffer, 0, newdata, shengyu_data.Length, bufLen);
                SplitData(shengyu_data.Length + bufLen, newdata);
                shengyu_data = null;
            }
            else
            {
                SplitData(bufLen, buffer);
            }
            //}
            //catch (Exception ex)
            //{
            //    Debug.Log("Receive Error:" + ex.Message);
            //    break;
            //}
        }
    }

    private byte[] shengyu_data = null;
    private void SplitData(int bufLen, byte[] data)
    {
        byte[] newdata = new byte[1024];

        byte[] lenbytes = new byte[4];
        Array.Copy(data, lenbytes, 4);
        int msglen = BitConverter.ToInt32(lenbytes, 0);
        if (msglen + 4 > bufLen)
        {
            byte[] ssdata = new byte[bufLen];
            Array.Copy(data, ssdata, bufLen);
            shengyu_data = ssdata;
        }
        else
        {
            byte[] bodybytes = new byte[msglen];
            Array.Copy(data, 4, bodybytes, 0, msglen);
            string clientcommand = Encoding.UTF8.GetString(bodybytes);
            Debug.Log("服务器收到:" + clientcommand);
            messages = clientcommand;
            if (bufLen > msglen + 4)
            {
                Array.Copy(data, 4 + msglen, newdata, 0, bufLen - msglen - 4);
                SplitData(bufLen - msglen - 4, newdata);
            }
        }
    }
    #region 测试用
    /// <summary>
    /// 测试用
    /// </summary>
#if UNITY_EDITOR
    private string btnName = "Start";
    private string msg = "";
    void OnGUI()
    {
        if (GUILayout.Button(btnName))
        {
            if (btnName == "Start")
            {
                btnName = "Stop";
                StartThread("127.0.0.1");
            }
            else
            {
                btnName = "Start";
                StopServer();
            }
        }
        GUILayout.Box(msg);
    }
    void Start()
    {
        receiveMessages += OnMessageReceive;
    }
    void OnMessageReceive(string str)
    {
        this.msg += str + "\n";
    }
#endif
    #endregion
}

客户端

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

public class Client : MonoBehaviour
{
    public static Client instence = null;

    public Action<string> receiveMessages;
    public Action<string> loginResult;

    private Socket clientSocket;
    private Thread thread;
    private string ipAdress = "";
    private int port = 9000;

    private string messages = null;
    private string loginMsg = null;


    void Awake()
    {
        if (instence == null)
        {
            instence = this;
            //StartThread("192.168.1.92");
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    void Update()
    {
        if (!string.IsNullOrEmpty(messages))
        {
            if (receiveMessages != null)
            {
                receiveMessages(messages);
            }
            messages = null;
        }
        if (!string.IsNullOrEmpty(loginMsg))
        {
            if (loginResult != null)
            {
                loginResult(loginMsg);
            }
            loginMsg = null;
        }
    }
    /// <summary>
    /// 关闭程序,结束线程
    /// </summary>
    void OnDestroy()
    {
        LogoutServer();
    }

    /// <summary>
    /// 开启线程用来登陆服务端
    /// </summary>
    /// <param name="ip"></param>
    public void StartThread(string ip)
    {
        Regex ipReg = new Regex(@"^[0-9]{1,3}(\.[0-9]{1,3}){3}$");
        if (!string.IsNullOrEmpty(ip) && ipReg.IsMatch(ip))
        {
            ipAdress = ip;
            thread = new Thread(new ThreadStart(ConnectServer));
            thread.Start();
        }
        else
        {
            Debug.Log("IPAdress is Error ! ! !");
        }
    }
    /// <summary>
    /// 登陆服务器
    /// </summary>
    void ConnectServer()
    {
        IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(ipAdress), port);
        clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            clientSocket.Connect(ipep);
            loginMsg = "Success";
        }
        catch (SocketException ex)
        {
            Debug.Log("connect error: " + ex.Message);
            loginMsg = ex.Message;
            return;
        }

        while (true)
        {
            //接收服务器信息
            int bufLen = 0;
            byte[] sdata = new byte[1024];
            try
            {
                bufLen = clientSocket.Receive(sdata);
                if (bufLen == 0)
                {
                    break;
                }
                if (shengyu_data != null)
                {
                    byte[] newdata = new byte[shengyu_data.Length + bufLen];
                    Array.Copy(shengyu_data, newdata, shengyu_data.Length);
                    Array.Copy(sdata, 0, newdata, shengyu_data.Length, bufLen);
                    SplitData(shengyu_data.Length + bufLen, newdata);
                    shengyu_data = null;
                }
                else
                {
                    SplitData(bufLen, sdata);
                }
            }
            catch (Exception ex)
            {
                Debug.Log("Receive Error:" + ex.Message);
                break;
            }
        }
        //clientSocket.Close();
        thread.Abort();
    }
    private byte[] shengyu_data = null;
    private void SplitData(int bufLen, byte[] data)
    {
        byte[] newdata = new byte[1024];

        byte[] lenbytes = new byte[4];
        Array.Copy(data, lenbytes, 4);
        int msglen = BitConverter.ToInt32(lenbytes, 0);
        if (msglen + 4 > bufLen)
        {
            byte[] ssdata = new byte[bufLen];
            Array.Copy(data, ssdata, bufLen);
            shengyu_data = ssdata;
        }
        else
        {
            byte[] bodybytes = new byte[msglen];
            Array.Copy(data, 4, bodybytes, 0, msglen);
            string clientcommand = Encoding.UTF8.GetString(bodybytes);
            Debug.Log("客户端收到:" + clientcommand);
            messages = clientcommand;
            if (bufLen > msglen + 4)
            {
                Array.Copy(data, 4 + msglen, newdata, 0, bufLen - msglen - 4);
                SplitData(bufLen - msglen - 4, newdata);
            }
        }
    }
    /// <summary>
    /// 登出服务端
    /// </summary>
    public void LogoutServer()
    {
        if (clientSocket != null)
        {
            clientSocket.Close();
            clientSocket = null;
        }
        if (thread != null)
        {
            thread.Abort();
            thread = null;
        }
    }
    /// <summary>
    /// 发送消息
    /// </summary>
    public void SendMessageToServer(string message)
    {
        int msgLen = Encoding.UTF8.GetByteCount(message);
        byte[] data = new byte[4 + msgLen];
        //把长度转成字节数组 
        byte[] lenbytes = BitConverter.GetBytes(msgLen);
        //把消息转为字节数组
        byte[] bodybytes = Encoding.UTF8.GetBytes(message);
        //将len添加到bytes数组中
        Array.Copy(lenbytes, data, 4);
        //将实际内容添加到bytes数组中
        Array.Copy(bodybytes, 0, data, 4, msgLen);
        if (clientSocket != null)
        {
            clientSocket.Send(data);
        }
    }
    #region 测试用
    /// <summary>
    /// 测试用
    /// </summary>
#if UNITY_EDITOR
    private string ipPath = "127.0.0.1";
    private bool login = true;
    private string content = "客户端发出的消息";
    void OnGUI()
    {
        if (login)
        {
            ipPath = GUILayout.TextArea(ipPath);
            if (GUILayout.Button("Login"))
            {
                if (clientSocket == null)
                {
                    login = false;
                    StartThread(ipPath);
                }
            }
        }
        else
        {
            content = GUILayout.TextField(content);
            if (GUILayout.Button("Send"))
            {
                SendMessageToServer(content);
            }
        }
    }
#endif
    #endregion
}

首先要注意的是Scoket是另外开启线程去使用的,这里有一个坑,就是Scoket传递数据的时候,不是在主线程传递的,所以在Unity里使用的话会报错,说这个数据不是在主线程。所以在发送数据的时候我在Update里把数据传出去。因为Update是在主线程里执行的。所以就避免了上面的问题。

其实,Socket仔细研究一下也挺有意思的,我在研究的时候经常会出现程序崩溃的现象。但是真正弄出来之后,心情会非常美丽的。最近对上面客户端代码进行了修改,因为经常在发送比较频繁的时候会出现粘包和丢包的问题,所以在发送消息和接受消息的时候,对这个问题进行处理。目前还没发现出什么问题。

You May Also Like

About the Author: 大腿Plus

1 Comment

发表评论