一篇Unity即当客户端又当服务端的文章
通讯协议TCP、UDP
服务端有许多重复的方法,感兴趣的小伙伴可以写成基类降低重复率
一、前言
实现通过TCP或UDP协议发送指定命令,接收端做出控制对象激活相关操作;
注意仔细看结尾总结;
Demo下载
二、TCP协议
客户端(发送端)
public class TCPSender : MonoBehaviour
{
[Header("目标IP地址")] public string ipAddress = "127.0.0.1";
[Header("目标端口号")] public int port = 12345;
[Header("发送内容")] public string hexCode = "FE 04 00 00 00 01 25 C5";
[Header("发送按钮")] public Button Btn_Send;
private TcpClient client;
private NetworkStream stream;
private void Start()
{
Btn_Send.onClick.AddListener(OnButtonClick);
client = new TcpClient(ipAddress, port);
}
public void OnButtonClick()
{
stream = client.GetStream();
byte[] data = Encoding.UTF8.GetBytes(hexCode);
stream.Write(data, 0, data.Length);
}
private void OnApplicationQuit()
{
client.Close();
}
}
服务端(接收端)
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
public class TCPSever : MonoBehaviour
{
private TcpListener tcpListener;
[Header("接收内容")] public string Response;
[Header("是否激活对象")] public bool IsActive;
[Header("待激活对象")] public GameObject[] OBJ;
[Header("服务器端口")] public int Port = 12345;
[Header("是否需要激活时间")] public bool IsActiveTime;
[Header("激活时间")] public float ActiveTime;
private float ActiveTimeTemp;
private bool isStart;
private UnityMainThreadDispatcher mainThreadDispatcher;
private void Start()
{
try
{
// 创建TCP监听器
tcpListener = new TcpListener(IPAddress.Any, Port);
tcpListener.Start();
Debug.Log("TCP服务器已启动,等待客户端连接...");
//开启主线程
mainThreadDispatcher = UnityMainThreadDispatcher.Instance;
// 在新线程中等待客户端连接
System.Threading.Thread acceptThread = new System.Threading.Thread(AcceptClients);
acceptThread.Start();
}
catch (Exception e)
{
Debug.LogError("发生错误: " + e.Message);
}
}
private void AcceptClients()
{
while (true)
{
// 等待客户端连接
TcpClient client = tcpListener.AcceptTcpClient();
Debug.Log("客户端已连接: " + ((IPEndPoint)client.Client.RemoteEndPoint).Address);
// 在新线程中处理客户端请求
System.Threading.Thread clientThread = new System.Threading.Thread(() => HandleClient(client));
clientThread.Start();
}
}
private void HandleClient(TcpClient client)
{
try
{
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
// 处理客户端发送的数据
string data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Debug.Log("接收到消息: " + data);
//处理消息
Client(data);
// 可以在这里回复客户端
string response = "服务器已接收消息: " + data;
byte[] responseBytes = Encoding.UTF8.GetBytes(response);
stream.Write(responseBytes, 0, responseBytes.Length);
}
client.Close();
}
catch (Exception e)
{
Debug.LogError("处理客户端请求时发生错误: " + e.Message);
}
}
private void OnApplicationQuit()
{
if (tcpListener != null)
{
tcpListener.Stop();
}
}
public void Client(string received)
{
if (received != null)
{
//表现
Response = received;
ComparisonResponse(received);
}
}
//处理请求
public void ComparisonResponse(string response)
{
switch (response)
{
case "FE 04 00 00 00 01 25 C5":
TimeOnclick();
break;
}
}
public void TimeOnclick()
{
if (IsActiveTime)
{
isStart = true;
ActiveTimeTemp = ActiveTime;
}
else
{
ObjActive();
}
}
public void ObjActive()
{
mainThreadDispatcher.Enqueue(() =>
{
foreach (GameObject o in OBJ)
{
o.SetActive(IsActive);
}
});
}
private void Update()
{
if (isStart)
{
ActiveTimeTemp -= Time.deltaTime;
if (ActiveTimeTemp <= 0)
{
ObjActive();
isStart = false;
}
}
}
}
三、UDP协议
客户端(发送端)
public class UdpSender : MonoBehaviour
{
[Header("目标IP地址")] public string ipAddress = "127.0.0.1";
[Header("目标端口号")] public int port = 12345;
[Header("发送内容")] public string hexCode = "FE 04 00 00 00 01 25 C5";
[Header("发送按钮")] public Button Btn_Send;
private UdpClient udpClient;
private void Start()
{
Btn_Send.onClick.AddListener(OnButtonClick);
}
public void OnButtonClick()
{
udpClient = new UdpClient(ipAddress, port);
byte[] data = Encoding.UTF8.GetBytes(hexCode);
udpClient.Send(data, data.Length);
}
private void OnApplicationQuit()
{
udpClient.Close();
}
}
服务器(接收端)
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class UDPSever : MonoBehaviour
{
private UdpClient udpServer;
[Header("接收内容")] public string Response;
[Header("是否激活对象")] public bool IsActive;
[Header("待激活对象")] public GameObject[] OBJ;
[Header("服务器端口")] public int Port = 12345;
[Header("是否需要激活时间")] public bool IsActiveTime;
[Header("激活时间")] public float ActiveTime;
private float ActiveTimeTemp;
private bool isStart;
private UnityMainThreadDispatcher mainThreadDispatcher;
private void Start()
{
try
{
// 启动UDP服务器
udpServer = new UdpClient(Port);
Debug.Log("UDP服务器已启动,等待客户端连接...");
mainThreadDispatcher = UnityMainThreadDispatcher.Instance;
// 在新线程中接收来自客户端的数据
IPEndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, Port);
StartServerReceiver(udpServer, clientEndPoint);
}
catch (Exception e)
{
Debug.LogError("发生错误: " + e.Message);
}
}
private void StartServerReceiver(UdpClient server, IPEndPoint clientEndPoint)
{
System.Threading.Thread serverThread = new System.Threading.Thread(() =>
{
while (true)
{
byte[] receivedData = server.Receive(ref clientEndPoint);
string receivedMessage = Encoding.UTF8.GetString(receivedData);
Client(receivedMessage);
// 在这里添加向客户端发送响应的代码
string responseMessage = "服务器已收到消息: " + receivedMessage;
byte[] responseData = Encoding.UTF8.GetBytes(responseMessage);
server.Send(responseData, responseData.Length, clientEndPoint);
}
});
serverThread.Start();
}
public void Client(string received)
{
if (received != null)
{
//表现
Response = received;
ComparisonResponse(received);
}
}
public void ComparisonResponse(string response)
{
switch (response)
{
case "FE 04 00 00 00 01 25 C5":
TimeOnclick();
break;
}
}
public void TimeOnclick()
{
if (IsActiveTime)
{
isStart = true;
ActiveTimeTemp = ActiveTime;
}
else
{
ObjActive();
}
}
public void ObjActive()
{
mainThreadDispatcher.Enqueue(() => {
foreach (GameObject o in OBJ)
{
o.SetActive(IsActive);
}
});
}
private void OnApplicationQuit()
{
udpServer.Close();
}
private void Update()
{
if (isStart)
{
ActiveTimeTemp -= Time.deltaTime;
if (ActiveTimeTemp <= 0)
{
ObjActive();
isStart = false;
}
}
}
}
四、总结
比如携程、Invoke函数、控制物体显隐必须在主线程进行操作,然后我们服务端监听客户端时间时候启用了多线程;如果不采用主线程则无法完成控制物体显隐的操作;
解决方案:通过创建主线程实现,以下脚本无需挂载直接实例化调用即可
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class UnityMainThreadDispatcher : MonoBehaviour
{
private static readonly Queue<Action> executionQueue = new Queue<Action>();
private static UnityMainThreadDispatcher _instance = null;
public static bool Exists
{
get {
return _instance != null; }
}
public static UnityMainThreadDispatcher Instance
{
get
{
if (!Exists)
{
_instance = new GameObject("MainThreadDispatcher").AddComponent<UnityMainThreadDispatcher>();
}
return _instance;
}
}
void Update()
{
while (executionQueue.Count > 0)
{
executionQueue.Dequeue().Invoke();
}
}
public void Enqueue(IEnumerator action)
{
executionQueue.Enqueue(() => {
StartCoroutine(action); });
}
public void Enqueue(Action action)
{
executionQueue.Enqueue(action);
}
}