局域网和广域网的数据通信,在平时做项目的时候经常会用到,之前一直用的是Unity3D自带的NetworkView。NetworkView虽然也可以用但是Unity5.x之后就过时了,而且专业版和个人版都会有连接个数限制,即使是专业版的连接数也是很少,而5.x新的网络通信功能Networking有没有仔细的研究过,所以目前来说比较好用的就是Socket了,下面是我自己根据网上的资料自己整理的代码。文章源自大腿Plus-https://www.shijunzh.com/archives/546
首先是服务端文章源自大腿Plus-https://www.shijunzh.com/archives/546
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 }
客户端文章源自大腿Plus-https://www.shijunzh.com/archives/546
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是在主线程里执行的。所以就避免了上面的问题。文章源自大腿Plus-https://www.shijunzh.com/archives/546
其实,Socket仔细研究一下也挺有意思的,我在研究的时候经常会出现程序崩溃的现象。但是真正弄出来之后,心情会非常美丽的。最近对上面客户端代码进行了修改,因为经常在发送比较频繁的时候会出现粘包和丢包的问题,所以在发送消息和接受消息的时候,对这个问题进行处理。目前还没发现出什么问题。文章源自大腿Plus-https://www.shijunzh.com/archives/546
来自外部的引用: 1