局域网和广域网的数据通信,在平时做项目的时候经常会用到,之前一直用的是Unity3D自带的NetworkView。NetworkView虽然也可以用但是Unity5.x之后就过时了,而且专业版和个人版都会有连接个数限制,即使是专业版的连接数也是很少,而5.x新的网络通信功能Networking有没有仔细的研究过,所以目前来说比较好用的就是Socket了,下面是我自己根据网上的资料自己整理的代码。
首先是服务端文章源自大腿Plus-https://www.zhaoshijun.com/archives/546
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
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.zhaoshijun.com/archives/546
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
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.zhaoshijun.com/archives/546
其实,Socket仔细研究一下也挺有意思的,我在研究的时候经常会出现程序崩溃的现象。但是真正弄出来之后,心情会非常美丽的。最近对上面客户端代码进行了修改,因为经常在发送比较频繁的时候会出现粘包和丢包的问题,所以在发送消息和接受消息的时候,对这个问题进行处理。目前还没发现出什么问题。文章源自大腿Plus-https://www.zhaoshijun.com/archives/546 文章源自大腿Plus-https://www.zhaoshijun.com/archives/546


来自外部的引用