以前使用UNet实现网络连接,Unity2018以后被弃用了。要将以前的老程序升到高版本,最开始打算使用Mirro,结果发现并不好用。那就只能自己写连接了。
1.TCP消息结构
(1). TCP消息是按流传输的,会发生粘包。那么在发射和接收消息时就需要对消息进行打包和解包。如果接收的消息长度不足,先不处理,继续接收。
(2).当TCP客户端断开时,服务端是收不到通知的。解决的方法是通过是否收到自定义消息来判断客户端是否在线。
这里用的消息结构如下,
第1部分为4个字节,表示消息长度,包括消息ID和消息体;
第2部分为2个字节,表示消息ID;
第3部分为n个字节,表示消息体;
2.辅助类和插件
(1). UnityThread:接收消息时在子线程中进行,处理消息后更新界面则只能在主线程中进行,这个类就是为了把实子线程中的有些操作放到主线程中。
(2). Newtonsoft.Json:这个插件可以实现Json字符串与Json对象之间的转换。
3.注意事项
(1). 将服务端和客户端设置为可后台运行Application.runInBackground = true
(2). UnityThread使用之前一定要初始化UnityThread.initUnityThread();
4.服务端代码
TcpServerScript .cs
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;public class TcpServerScript : MonoBehaviour
{public Image imageTcpServerStatus;public Text textConnectCount;public Text textPrompt;//上一次接收到消息时间,客户端是否在线以本数组为准Dictionary<int, TcpClientInfo> dictConnectId2Client;public Dictionary<int, float> dictConnectId2Time;private Dictionary<int, ClientRemainData> dictConnectId2RemainData;private int port = 6001;/// <summary>private TcpListener tcpListener;private bool running = false;/// <summary>/// Background thread for TcpServer workload. /// </summary> private Thread tcpListenerThread;int globalConnectId = 1;void Awake(){UnityThread.initUnityThread();}void Start(){dictConnectId2Time = new Dictionary<int, float>();dictConnectId2Client = new Dictionary<int, TcpClientInfo>();dictConnectId2RemainData = new Dictionary<int, ClientRemainData>();StartCoroutine(delayStartTcpServer());}IEnumerator delayStartTcpServer(){yield return new WaitForSeconds(0.5f);try{tcpListener = new TcpListener(IPAddress.Any, 6001);tcpListener.Start();imageTcpServerStatus.color = Color.green;}catch (Exception ex){imageTcpServerStatus.color = Color.gray;Debug.Log(ex.Message);yield break;}tcpListenerThread = new Thread(new ThreadStart(ListenForIncommingRequests));tcpListenerThread.IsBackground = true;tcpListenerThread.Start();}private void Update(){if (Time.frameCount % 10 == 0){List<int> keys = dictConnectId2Time.Keys.ToList();for (int i = 0; i < keys.Count; i++){int connectId = keys[i];if (Time.time - dictConnectId2Time[connectId] > 3.0f){OnServerDisconnected(connectId);}}//StringMsg msg = new StringMsg();//msg.str = "hello";//keys = dictConnectId2Client.Keys.ToList();//for (int i = 0; i < keys.Count; i++)//{// int key = keys[i];// ServerSendOne(dictConnectId2Client[key].client,MessageId.MsgId_StringMsg, msg);//}}textConnectCount.text = string.Format("连接数:{0}", dictConnectId2Client.Count);}private void ListenForIncommingRequests(){running = true;ThreadPool.QueueUserWorkItem(this.ListenerWorker, null);}private void ListenerWorker(object token){while (running){TcpClient connectedTcpClient = tcpListener.AcceptTcpClient();TcpClientInfo clientInfo = new TcpClientInfo(connectedTcpClient, globalConnectId);UnityThread.executeInUpdate(() =>{if (!dictConnectId2Client.ContainsKey(clientInfo.connectionId)){dictConnectId2Client.Add(clientInfo.connectionId, clientInfo); IPEndPoint endPoint = connectedTcpClient.Client.RemoteEndPoint as IPEndPoint;string clientIp = endPoint.Address.ToString();Debug.Log(clientIp);}});Debug.Log("连接:" + dictConnectId2Client.Count);ThreadPool.QueueUserWorkItem(this.HandleClientWorker, clientInfo);}tcpListener.Stop();}private void HandleClientWorker(object token){var clientInfo = token as TcpClientInfo;int connId = clientInfo.connectionId;if (!dictConnectId2RemainData.ContainsKey(clientInfo.connectionId)){dictConnectId2RemainData.Add(clientInfo.connectionId, new ClientRemainData(clientInfo));}using (var client = clientInfo.client as TcpClient)using (var nwStream = client.GetStream()){while (running){byte[] bufNumber = new byte[1000];int byReadNumber = nwStream.Read(bufNumber, 0, 1000);if (byReadNumber < 1){dictConnectId2Client.Remove(clientInfo.connectionId);Debug.Log("断开1:" + clientInfo.connectionId);break;}byte[] btsAdd = new byte[dictConnectId2RemainData[connId].lenRemain + byReadNumber];if (dictConnectId2RemainData[connId].lenRemain > 0){//拼接上次处理的字节Array.Copy(dictConnectId2RemainData[connId].btsRemain, 0, btsAdd, 0, dictConnectId2RemainData[connId].lenRemain);}Array.Copy(bufNumber, 0, btsAdd, dictConnectId2RemainData[connId].lenRemain, byReadNumber);List<byte> listRemain = new List<byte>();dealwithData(clientInfo, btsAdd, listRemain);if (listRemain.Count > 0){byte[] btsTemp = listRemain.ToArray();Array.Copy(btsTemp, 0, dictConnectId2RemainData[connId].btsRemain, 0, btsTemp.Length);dictConnectId2RemainData[connId].lenRemain = btsTemp.Length;}else