電腦端啟動后自動廣播自身存在,手機端啟動后監聽廣播并發現服務器。
發現后自動建立 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();}
}