以前使用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