Unity 实现手机端和电脑项目在局域网内通信
电脑端启动后自动广播自身存在,手机端启动后监听广播并发现服务器。
发现后自动建立 UDP 连接,双方可互发消息。
内置心跳检测,网络中断时会自动检测并提示断开
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections.Generic;public class ComputerServer : MonoBehaviour
{private const int broadcastPort = 1369;private const int communicationPort = 1370; // 通信端口private UdpClient broadcastClient;private UdpClient commClient;private Thread broadcastThread;private Thread receiveThread;private IPEndPoint clientEndpoint;private bool isRunning = true;private float lastHeartbeatTime;private const float heartbeatInterval = 2f; // 心跳间隔private const float timeoutThreshold = 5f; // 超时阈值// 主线程任务队列private Queue<System.Action> mainThreadActions = new Queue<System.Action>();void Start(){// 初始化广播客户端broadcastClient = new UdpClient();broadcastThread = new Thread(BroadcastMessage);broadcastThread.IsBackground = true;broadcastThread.Start();// 初始化通信客户端commClient = new UdpClient(communicationPort);receiveThread = new Thread(ReceiveMessages);receiveThread.IsBackground = true;receiveThread.Start();// 主线程初始化时间lastHeartbeatTime = Time.time;}// 将操作放入主线程队列private void RunOnMainThread(System.Action action){lock (mainThreadActions){mainThreadActions.Enqueue(action);}}void BroadcastMessage(){while (isRunning){try{string message = "ServerHere";byte[] data = Encoding.UTF8.GetBytes(message);IPEndPoint broadcastEndpoint = new IPEndPoint(IPAddress.Broadcast, broadcastPort);broadcastClient.Send(data, data.Length, broadcastEndpoint);Debug.Log("Broadcast sent: ServerHere");Thread.Sleep(1000);}catch (System.Exception e){Debug.LogError("Broadcast error: " + e.Message);}}}void ReceiveMessages(){while (isRunning){try{IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);byte[] data = commClient.Receive(ref sender);string message = Encoding.UTF8.GetString(data);// 处理心跳包 - 使用主线程更新时间if (message == "Heartbeat"){// 将时间更新操作放入主线程队列RunOnMainThread(() => {lastHeartbeatTime = Time.time;});Debug.Log("Received heartbeat from client");SendMessageToClient("HeartbeatAck"); // 回复心跳确认}else{Debug.Log("Received from client: " + message);// 处理客户端发送的业务消息}// 记录客户端端点(首次连接时)if (clientEndpoint == null){clientEndpoint = sender;}}catch (SocketException e){if (e.SocketErrorCode == SocketError.Interrupted){Debug.Log("Receive thread interrupted");}else{Debug.LogError("Receive error: " + e.Message + " (Code: " + e.SocketErrorCode + ")");}}catch (System.Exception e){Debug.LogError("Receive error: " + e.Message);}}}// 发送消息到客户端public void SendMessageToClient(string message){if (clientEndpoint == null){Debug.LogWarning("No client connected");return;}try{byte[] data = Encoding.UTF8.GetBytes(message);commClient.Send(data, data.Length, clientEndpoint);}catch (System.Exception e){Debug.LogError("Send error: " + e.Message);}}void Update(){if (Input.GetKeyDown(KeyCode.Tab))SendMessageToClient("我是服务端");// 处理主线程任务队列while (mainThreadActions.Count > 0){System.Action action;lock (mainThreadActions){action = mainThreadActions.Dequeue();}action.Invoke();}// 检测客户端超时if (clientEndpoint != null && Time.time - lastHeartbeatTime > timeoutThreshold){Debug.LogWarning("Client timeout, disconnect detected");clientEndpoint = null; // 重置连接状态}// 定时发送心跳(如果已连接)if (clientEndpoint != null && Time.time - lastHeartbeatTime > heartbeatInterval){SendMessageToClient("Heartbeat");lastHeartbeatTime = Time.time;}}void OnDestroy(){isRunning = false;// 安全终止线程if (broadcastThread != null && broadcastThread.IsAlive){broadcastThread.Interrupt();}if (receiveThread != null && receiveThread.IsAlive){receiveThread.Interrupt();}// 关闭客户端broadcastClient?.Close();commClient?.Close();}
}
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.UI;
using System.Collections.Generic;//按下TAb发送消息public class MobileClient : MonoBehaviour
{private const int broadcastPort = 1369;private const int communicationPort = 1370;private UdpClient broadcastClient;private UdpClient commClient;private Thread broadcastThread;private Thread receiveThread;private string serverIP = null;private IPEndPoint serverEndpoint;private bool isRunning = true;private float lastHeartbeatTime;private const float heartbeatInterval = 2f;private const float timeoutThreshold = 5f;// 主线程任务队列private Queue<System.Action> mainThreadActions = new Queue<System.Action>();public Text statusText;public InputField messageInput;void Start(){try{broadcastClient = new UdpClient(broadcastPort);broadcastClient.EnableBroadcast = true;broadcastThread = new Thread(ReceiveBroadcast);broadcastThread.IsBackground = true;broadcastThread.Start();// 在主线程初始化时间lastHeartbeatTime = Time.time;RunOnMainThread(() => {statusText.text = "Searching for server...";});}catch (System.Exception e){Debug.LogError("Initialization error: " + e.Message);RunOnMainThread(() => {statusText.text = "Initialization failed";});}}private void RunOnMainThread(System.Action action){lock (mainThreadActions){mainThreadActions.Enqueue(action);}}void ReceiveBroadcast(){while (isRunning && serverIP == null){try{IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);byte[] data = broadcastClient.Receive(ref anyIP);string message = Encoding.UTF8.GetString(data);if (message == "ServerHere"){lock (this){if (IPAddress.TryParse(anyIP.Address.ToString(), out IPAddress parsedIp)){serverIP = parsedIp.ToString();serverEndpoint = new IPEndPoint(parsedIp, communicationPort);RunOnMainThread(() => {statusText.text = "Found server: " + serverIP;});Debug.Log("Server IP found: " + serverIP);InitializeCommunication();}else{Debug.LogError("Invalid server IP address");}}}}catch (System.Exception e){Debug.LogError("Broadcast receive error: " + e.Message);}}}void InitializeCommunication(){try{commClient = new UdpClient(new IPEndPoint(IPAddress.Any, communicationPort + 1));receiveThread = new Thread(ReceiveServerMessages);receiveThread.IsBackground = true;receiveThread.Start();RunOnMainThread(() => {statusText.text = "Connected to server";});SendMessageToServer("Client connected");}catch (System.Exception e){Debug.LogError("Communication initialization error: " + e.Message);RunOnMainThread(() => {statusText.text = "Failed to connect";});}}void ReceiveServerMessages(){while (isRunning && serverEndpoint != null){try{IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);byte[] data = commClient.Receive(ref sender);if (sender.Address.ToString() == serverEndpoint.Address.ToString() &&sender.Port == serverEndpoint.Port){string message = Encoding.UTF8.GetString(data);if (message == "Heartbeat"){// 将时间更新操作放到主线程RunOnMainThread(() => {lastHeartbeatTime = Time.time;});SendMessageToServer("HeartbeatAck");Debug.Log("Received server heartbeat");}else if (message == "HeartbeatAck"){// 将时间更新操作放到主线程RunOnMainThread(() => {lastHeartbeatTime = Time.time;});Debug.Log("Received heartbeat ack");}else{Debug.Log("Received from server: " + message);string msg = message;RunOnMainThread(() => {statusText.text = "Server: " + msg;});}}else{Debug.LogWarning("Received message from unknown source: " + sender.Address);}}catch (SocketException e){if (e.SocketErrorCode == SocketError.Interrupted){Debug.Log("Receive thread interrupted");}else{Debug.LogError("Communication socket error: " + e.Message + " (Code: " + e.SocketErrorCode + ")");}}catch (System.Exception e){Debug.LogError("Communication receive error: " + e.Message);}}}public void SendMessageToServer(string message){if (serverEndpoint == null || commClient == null){Debug.LogWarning("No server connected");return;}try{byte[] data = Encoding.UTF8.GetBytes(message);commClient.Send(data, data.Length, serverEndpoint);}catch (System.Exception e){Debug.LogError("Send to server error: " + e.Message);}}public void OnSendButtonClick(){if (!string.IsNullOrEmpty(messageInput.text)){SendMessageToServer(messageInput.text);messageInput.text = "";}}void Update(){// 处理主线程队列while (mainThreadActions.Count > 0){System.Action action;lock (mainThreadActions){action = mainThreadActions.Dequeue();}action.Invoke();}if (serverIP != null){// 检测服务器超时if (Time.time - lastHeartbeatTime > timeoutThreshold){RunOnMainThread(() => {statusText.text = "Server disconnected";});Debug.LogWarning("Server timeout");}// 发送心跳else if (Time.time - lastHeartbeatTime > heartbeatInterval){SendMessageToServer("Heartbeat");lastHeartbeatTime = Time.time;}}if (Input.GetKeyDown(KeyCode.Tab))SendMessageToServer("我是客户端");}void OnDestroy(){isRunning = false;if (broadcastThread != null && broadcastThread.IsAlive){broadcastThread.Interrupt();}if (receiveThread != null && receiveThread.IsAlive){receiveThread.Interrupt();}broadcastClient?.Close();commClient?.Close();}
}