Unity安卓设备局域网通信实战:TCP/UDP混合方案与工程实现

📅 2026/8/2 21:25:31
Unity安卓设备局域网通信实战:TCP/UDP混合方案与工程实现
1. 项目概述为什么要在Unity里折腾局域网通信如果你做过Unity移动端项目尤其是需要多台安卓设备联动的场景比如同屏互动游戏、多屏展示、教育培训应用或者本地化数据同步工具那你肯定遇到过这个问题怎么让这些设备在不依赖外网服务器的情况下自己“聊”起来这就是我们今天要深入探讨的——在Unity中实现安卓设备间的局域网通信。这听起来像是网络编程的活儿和Unity这个游戏引擎有什么关系关系大了。Unity的优势在于跨平台一套代码可以跑在PC、手机、主机上。当你的应用场景限定在某个封闭的物理空间内比如一个展厅、一间教室、一场线下活动让现场的安卓手机或平板通过Wi-Fi自组网络进行数据交换能带来极致的低延迟体验并且完全规避了公网服务器的流量、成本和单点故障风险。想象一下一个博物馆的导览应用游客的手机靠近某个展品时自动从附近的主控平板获取该展品的详细资料和AR模型这个过程流畅且无需消耗手机流量体验是不是很棒然而Unity本身并没有提供一个开箱即用的、针对移动端局域网通信的高级API。官方Netcode for GameObjects或者Transport Layer更多是为有中心服务器或点对点P2P的互联网对战设计的在纯局域网、特别是安卓设备自动发现和直连的场景下显得有些“重”且配置复杂。因此我们需要自己动手利用一些更底层的、或经过验证的第三方方案来搭建这个通信桥梁。这不仅仅是调用几个API更涉及到网络协议选择、设备发现、连接管理、数据序列化等一系列实际工程问题。接下来我就结合自己趟过的坑把这套流程给你拆解明白。2. 核心方案选型TCP、UDP还是第三方库动手之前第一个灵魂拷问就是用什么协议这直接决定了通信的可靠性、效率和实现复杂度。主要就三条路TCP、UDP、或者用成熟的第三方库如LiteNetLib、Forge Networking。2.1 TCP可靠但稍显笨重TCP就像打电话建立连接后能保证你发送的数据包按顺序、不丢失地到达对方。在Unity里你可以直接用System.Net.Sockets命名空间下的TcpClient和TcpListener来操作。为什么选它可靠性为王对于需要确保每一个指令、每一条聊天消息、每一次状态同步都准确无误的场景TCP是首选。比如你开发一个局域网象棋游戏落子坐标绝对不能丢或乱序。流式数据TCP是面向流的你可以方便地传输不定长的数据比如一个JSON字符串或一段序列化的类对象。实现直观.NET框架原生支持概念清晰学习成本相对较低。需要注意的坑连接管理你需要自己写服务端一个设备作为主机来监听端口以及客户端来连接。在安卓上主机设备需要获取本地IP地址并广播给其他设备。阻塞操作默认的Read/Write是阻塞的如果在主线程做会卡住整个游戏。必须使用异步方法BeginRead/BeginWrite或者放到单独的线程/任务Task中处理。NAT与防火墙在有些路由器或企业网络环境下设备可能处于不同的NAT后简单的内网IP直连可能会失败。不过对于大部分由同一路由器创建的Wi-Fi局域网这个问题不突出。2.2 UDP快速但不可靠UDP则像发广播或寄明信片你把数据包发出去不保证对方一定能收到也不保证按顺序收到。在Unity中对应UdpClient。为什么选它速度与低开销没有建立连接和确认机制延迟极低开销小。非常适合实时性要求高、允许偶尔丢包的场景比如多人游戏的玩家位置同步快照插值、语音聊天。广播与组播UDP可以很方便地向整个子网广播消息这是实现设备自动发现的利器。一台设备喊一嗓子“我在这里”所有监听同一端口的设备都能听到。需要注意的坑可靠性需自实现如果你需要可靠传输得在应用层自己实现确认重传、序列号等机制复杂度陡增。数据包大小限制单个UDP数据包不宜过大通常不超过MTU约1500字节否则容易分片丢失。传输大文件或长消息需要分包和组包逻辑。无序到达后发的包可能先到处理状态同步时要考虑这一点。2.3 第三方库站在巨人的肩膀上如果你不想重复造轮子特别是需要更高级功能如NAT穿透、更优雅的会话管理时第三方库是绝佳选择。LiteNetLib这是一个轻量级、高性能的C#网络库在Unity社区非常受欢迎。它基于UDP但自己实现了一套可靠的、不可靠的、有序的等多种信道相当于把TCP和UDP的优点结合了。它内置了简单的NAT穿透和连接管理用起来比裸写Socket舒服很多。Forge Networking (现为MLAPI)这是一个更重量级的网络解决方案提供了完整的网络对象、RPC远程过程调用、序列化等高层抽象。如果你项目的网络逻辑非常复杂它可能更合适但学习曲线也更陡峭。选型建议追求快速原型、小规模可靠通信从TCP开始理解基本原理。需要设备自动发现、实时性要求高重点考虑UDP广播发现 LiteNetLib进行实际数据传输。项目中重度依赖网络、团队有精力学习评估Forge/MLAPI或Unity Netcode。为了兼顾教学意义和实用性下文我将以“TCP实现可靠通信” “UDP广播实现设备发现”这个混合方案作为主线进行详解这也是很多实际项目采用的折中且有效的策略。3. 实战架构设计客户端与服务器角色在局域网通信中设备通常有两种角色服务器主机和客户端。有时一台设备需要兼具两种角色Peer-to-Peer但为了逻辑清晰我们先从典型的C/S架构说起。服务器主机职责启动一个TcpListener持续监听指定端口如 9050。启动一个UdpClient监听另一个端口如 9051用于接收客户端的广播发现请求。维护一个已连接的客户端列表。接收客户端消息并可能广播给其他所有客户端如聊天室或进行一对一回复。客户端职责启动时通过UdpClient向局域网广播“寻找服务器”的消息。收到服务器的回应后获取服务器的IP和端口。根据获取的地址尝试用TcpClient连接服务器。连接成功后进行可靠的数据收发。关键设计点双端口策略TCP端口用于主力数据传输UDP端口用于发现。这样可以避免功能耦合。异步无处不在所有网络操作监听、接受连接、读写数据都必须使用异步模式防止阻塞主线程。Unity的主线程是渲染和游戏逻辑线程一旦被阻塞游戏就会卡顿甚至无响应。心跳机制为了检测连接是否意外断开如设备突然关机、Wi-Fi断开需要实现一个简单的心跳包。客户端定期如每5秒向服务器发送一个小数据包服务器长时间收不到某个客户端的心跳则判定其离线并清理资源。4. 核心实现步骤拆解下面我们分模块实现这个通信系统。我会提供关键代码片段并解释其意图。4.1 设备发现UDP广播与响应这是让设备自动找到彼此的关键。我们让服务器监听UDP广播客户端开机后发送广播。服务器端监听发现using System.Net; using System.Net.Sockets; using System.Threading; using UnityEngine; public class DiscoveryServer : MonoBehaviour { private UdpClient udpListener; private Thread listenThread; private bool isRunning false; private int discoveryPort 9051; // 发现端口 private string serverResponse SERVER_HERE; // 响应标识 void Start() { StartDiscoveryServer(); } void StartDiscoveryServer() { isRunning true; udpListener new UdpClient(discoveryPort); listenThread new Thread(new ThreadStart(ListenForBroadcasts)); listenThread.IsBackground true; listenThread.Start(); Debug.Log(发现服务器已启动监听端口: discoveryPort); } void ListenForBroadcasts() { IPEndPoint remoteEndPoint new IPEndPoint(IPAddress.Any, 0); while (isRunning) { try { // 异步接收改为同步接收在线程中使用更简单 byte[] receivedBytes udpListener.Receive(ref remoteEndPoint); string receivedMessage System.Text.Encoding.UTF8.GetString(receivedBytes); if (receivedMessage DISCOVER_SERVER) // 客户端发来的发现请求 { Debug.Log($收到来自 {remoteEndPoint.Address} 的发现请求); // 获取本机在局域网内的IP可能有多个取合适的 string localIP GetLocalIPAddress(); byte[] responseBytes System.Text.Encoding.UTF8.GetBytes(${serverResponse}|{localIP}); udpListener.Send(responseBytes, responseBytes.Length, remoteEndPoint); } } catch (SocketException e) { // 通常由关闭UdpClient引起可忽略 if (isRunning) Debug.LogWarning(发现监听异常: e.Message); } } } string GetLocalIPAddress() { // 注意在安卓设备上获取的可能是多个地址如WLAN、蜂窝网络。 // 这里简单返回第一个IPv4地址生产环境需要更精确的判断。 var host Dns.GetHostEntry(Dns.GetHostName()); foreach (var ip in host.AddressList) { if (ip.AddressFamily AddressFamily.InterNetwork) // IPv4 { return ip.ToString(); } } return 127.0.0.1; } void OnDestroy() { isRunning false; if (udpListener ! null) { udpListener.Close(); } if (listenThread ! null listenThread.IsAlive) { listenThread.Join(1000); // 等待线程结束 } } }客户端广播寻找服务器public class DiscoveryClient : MonoBehaviour { private UdpClient udpClient; private int discoveryPort 9051; private string broadcastMessage DISCOVER_SERVER; private IPEndPoint broadcastEndPoint; void Start() { // 广播地址255.255.255.255 或 特定子网广播地址 broadcastEndPoint new IPEndPoint(IPAddress.Broadcast, discoveryPort); udpClient new UdpClient(); udpClient.EnableBroadcast true; // 关键启用广播 udpClient.Client.ReceiveTimeout 2000; // 设置接收超时2秒 SendDiscoveryRequest(); } void SendDiscoveryRequest() { byte[] requestBytes System.Text.Encoding.UTF8.GetBytes(broadcastMessage); udpClient.Send(requestBytes, requestBytes.Length, broadcastEndPoint); Debug.Log(已发送发现广播); // 异步等待响应 udpClient.BeginReceive(OnDiscoveryResponseReceived, null); } private void OnDiscoveryResponseReceived(System.IAsyncResult ar) { try { IPEndPoint remoteEndPoint new IPEndPoint(IPAddress.Any, 0); byte[] receivedBytes udpClient.EndReceive(ar, ref remoteEndPoint); string response System.Text.Encoding.UTF8.GetString(receivedBytes); if (response.StartsWith(SERVER_HERE)) { string[] parts response.Split(|); if (parts.Length 2) { string serverIP parts[1]; int serverPort 9050; // 假设TCP服务在9050端口 Debug.Log($发现服务器IP: {serverIP}); // 通知主逻辑连接服务器 MainLogic.Instance.ConnectToServer(serverIP, serverPort); } } } catch (SocketException ex) { Debug.Log(发现服务器超时或失败: ex.Message); // 可以在这里重试 } finally { udpClient.Close(); } } }注意UDP广播在有些路由器或网络配置下可能被禁用。此外IPAddress.Broadcast(255.255.255.255) 不一定在所有网络中都有效有时需要计算子网广播地址如 192.168.1.255。安卓设备上获取本地IP也需要更精细的处理避免拿到虚拟或蜂窝网络的IP。4.2 可靠通信TCP连接与数据收发发现服务器后客户端通过TCP建立可靠连接。服务器端TCP监听与客户端管理public class TcpCommunicationServer : MonoBehaviour { private TcpListener tcpListener; private ListClientHandler connectedClients new ListClientHandler(); private int tcpPort 9050; private Thread listenThread; void Start() { StartTcpServer(); } void StartTcpServer() { try { tcpListener new TcpListener(IPAddress.Any, tcpPort); tcpListener.Start(); Debug.Log($TCP服务器启动监听端口 {tcpPort}); listenThread new Thread(new ThreadStart(ListenForClients)); listenThread.IsBackground true; listenThread.Start(); } catch (Exception e) { Debug.LogError(启动TCP服务器失败: e.Message); } } void ListenForClients() { while (true) { try { TcpClient newClient tcpListener.AcceptTcpClient(); // 阻塞直到有客户端连接 Debug.Log($新客户端连接: {((IPEndPoint)newClient.Client.RemoteEndPoint).Address}); ClientHandler handler new ClientHandler(newClient, this); connectedClients.Add(handler); Thread clientThread new Thread(new ThreadStart(handler.HandleCommunication)); clientThread.IsBackground true; clientThread.Start(); } catch (SocketException) { // 监听器被关闭时抛出正常退出循环 break; } } } // 广播消息给所有客户端非主线程安全需要加锁或通过队列 public void BroadcastMessage(string message, ClientHandler excludeClient null) { byte[] data System.Text.Encoding.UTF8.GetBytes(message); lock (connectedClients) // 加锁防止列表被修改 { foreach (var client in connectedClients) { if (client ! excludeClient client.IsConnected) { client.SendData(data); } } } } // 客户端断开时调用 public void RemoveClient(ClientHandler client) { lock (connectedClients) { connectedClients.Remove(client); } Debug.Log(客户端断开当前连接数: connectedClients.Count); } void OnDestroy() { if (tcpListener ! null) { tcpListener.Stop(); } // 清理所有客户端连接 lock (connectedClients) { foreach (var client in connectedClients) { client.Disconnect(); } connectedClients.Clear(); } } } // 处理单个客户端连接的类 public class ClientHandler { private TcpClient tcpClient; private NetworkStream stream; private TcpCommunicationServer server; private byte[] buffer new byte[4096]; public bool IsConnected { get; private set; } true; public ClientHandler(TcpClient client, TcpCommunicationServer server) { this.tcpClient client; this.stream client.GetStream(); this.server server; } public void HandleCommunication() { try { while (IsConnected) { int bytesRead stream.Read(buffer, 0, buffer.Length); // 阻塞读取 if (bytesRead 0) { // 连接已关闭 break; } string receivedMessage System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead); Debug.Log($收到消息: {receivedMessage}); // 处理消息例如原样广播 server.BroadcastMessage($客户端说: {receivedMessage}, this); // 这里可以解析消息执行不同的逻辑如RPC调用 } } catch (IOException) { // 连接中断 } catch (Exception e) { Debug.LogWarning(处理客户端通信时异常: e.Message); } finally { Disconnect(); } } public void SendData(byte[] data) { try { stream.Write(data, 0, data.Length); } catch { IsConnected false; } } public void Disconnect() { IsConnected false; stream?.Close(); tcpClient?.Close(); server.RemoveClient(this); } }客户端TCP连接与消息发送public class TcpCommunicationClient : MonoBehaviour { private TcpClient tcpClient; private NetworkStream stream; private byte[] receiveBuffer new byte[4096]; private bool isConnected false; public void ConnectToServer(string ip, int port) { try { tcpClient new TcpClient(); // 异步连接避免主线程卡顿 tcpClient.BeginConnect(ip, port, new AsyncCallback(ConnectCallback), tcpClient); } catch (Exception e) { Debug.LogError(连接失败: e.Message); } } private void ConnectCallback(IAsyncResult ar) { try { tcpClient.EndConnect(ar); if (tcpClient.Connected) { stream tcpClient.GetStream(); isConnected true; Debug.Log(成功连接到服务器); // 开始异步接收数据 stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, ReceiveCallback, null); // 可以在这里发送登录或初始消息 SendMessage(Hello Server!); } } catch (Exception e) { Debug.LogError(连接回调异常: e.Message); } } private void ReceiveCallback(IAsyncResult ar) { if (!isConnected) return; try { int bytesRead stream.EndRead(ar); if (bytesRead 0) { string message System.Text.Encoding.UTF8.GetString(receiveBuffer, 0, bytesRead); Debug.Log(收到服务器消息: message); // 注意Unity API不能在子线程调用需要将消息派发到主线程处理 UnityMainThreadDispatcher.Instance.Enqueue(() { // 在这里更新UI或游戏状态例如显示聊天内容 ChatUI.Instance.AppendMessage(message); }); // 继续接收下一条消息 stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, ReceiveCallback, null); } else { // 连接关闭 Disconnect(); } } catch { Disconnect(); } } public void SendMessage(string message) { if (!isConnected) return; byte[] data System.Text.Encoding.UTF8.GetBytes(message); try { stream.BeginWrite(data, 0, data.Length, new AsyncCallback(SendCallback), null); } catch { Disconnect(); } } private void SendCallback(IAsyncResult ar) { try { stream.EndWrite(ar); } catch { // 发送失败 } } private void Disconnect() { isConnected false; stream?.Close(); tcpClient?.Close(); Debug.Log(与服务器断开连接); } void OnDestroy() { Disconnect(); } }4.3 数据序列化与协议设计直接发送字符串对于简单场景够用但复杂应用需要传输结构化的数据如玩家位置、状态、自定义指令。这时就需要设计一个简单的应用层协议。常见方案JSON使用JsonUtility(Unity自带) 或Newtonsoft.Json(需导入) 将对象序列化为字符串。优点是易读、易调试兼容性好。缺点是体积相对较大解析有一定开销。[System.Serializable] public class PlayerData { public string id; public Vector3 position; public int score; } // 发送 PlayerData data new PlayerData(); string json JsonUtility.ToJson(data); byte[] bytes System.Text.Encoding.UTF8.GetBytes(json); // 接收 string receivedJson System.Text.Encoding.UTF8.GetString(bytes); PlayerData receivedData JsonUtility.FromJsonPlayerData(receivedJson);Protobuf (Google.Protobuf)二进制序列化体积小速度快跨语言支持好。但需要预先定义.proto文件并生成C#类流程稍复杂。MessagePack另一种高效的二进制序列化格式在Unity社区也很流行比JSON快且小。协议帧设计为了防止TCP的“粘包”问题多个消息在接收端被合并成一次接收通常需要在数据前加一个头部指明后面数据的长度。// 发送端先发长度再发数据 byte[] data System.Text.Encoding.UTF8.GetBytes(message); byte[] lengthPrefix BitConverter.GetBytes(data.Length); stream.Write(lengthPrefix, 0, 4); // 假设长度用4字节int表示 stream.Write(data, 0, data.Length); // 接收端先读4字节获取长度再读取指定长度的数据 byte[] lengthBuffer new byte[4]; stream.Read(lengthBuffer, 0, 4); int messageLength BitConverter.ToInt32(lengthBuffer, 0); byte[] messageBuffer new byte[messageLength]; stream.Read(messageBuffer, 0, messageLength); string message System.Text.Encoding.UTF8.GetString(messageBuffer);在异步读写中处理这种定长头部需要更精细的状态管理是网络编程中的一个常见难点。5. Unity与安卓平台的适配要点把代码从编辑器搬到真机尤其是安卓平台会有一系列特有的问题。5.1 权限问题安卓应用要进行网络通信必须在AndroidManifest.xml中添加网络权限。如果你使用Unity通常需要在Player Settings中设置或者手动修改生成的Manifest文件。必需权限INTERNET(允许网络访问) 和ACCESS_NETWORK_STATE(检查网络连接状态)。如果使用Wi-Fi相关功能如获取SSID可能需要ACCESS_WIFI_STATE和CHANGE_WIFI_MULTICAST_STATE用于UDP组播。在Unity中最简单的方法是在Edit - Project Settings - Player - Android Settings - Other Settings中找到Configuration部分在Write Permissions里勾选Internet Access和Access Network State。5.2 后台运行与省电策略安卓系统为了省电会在应用进入后台时限制甚至停止其网络活动。如果你的应用需要持续通信如作为主机服务器需要采取一些措施前台服务启动一个前台服务并在通知栏显示一个持续的通知告诉系统你的应用正在执行重要任务。WakeLock谨慎使用WakeLock来阻止CPU休眠。过度使用会严重影响设备续航需权衡利弊。使用WorkManager或JobScheduler对于非实时性的定时网络任务可以使用系统提供的调度器。5.3 多线程与Unity APIUnity的绝大多数API如GameObject.Find,Transform.position,Debug.Log都不是线程安全的只能在主线程调用。我们的网络接收回调通常发生在后台线程。解决方案使用主线程分发器创建一个单例类内部维护一个主线程执行的Action队列。public class UnityMainThreadDispatcher : MonoBehaviour { private static UnityMainThreadDispatcher instance; private QueueSystem.Action actions new QueueSystem.Action(); public static UnityMainThreadDispatcher Instance { get { if (instance null) { GameObject go new GameObject(MainThreadDispatcher); instance go.AddComponentUnityMainThreadDispatcher(); DontDestroyOnLoad(go); } return instance; } } void Update() { lock (actions) { while (actions.Count 0) { actions.Dequeue().Invoke(); } } } public void Enqueue(System.Action action) { lock (actions) { actions.Enqueue(action); } } }在网络线程收到数据后将处理逻辑包装成Action通过UnityMainThreadDispatcher.Instance.Enqueue(() { ... })放入队列由Update执行。5.4 网络状态检测在局域网通信中Wi-Fi的断开重连是常事。应用需要能够检测网络变化并做出响应如重连、提示用户。使用Application.internetReachability可以粗略判断网络可达性。对于更精细的Wi-Fi状态在安卓上可能需要通过AndroidJavaClass调用原生API。6. 性能优化与调试技巧6.1 流量与性能优化减少发送频率对于实时位置同步不要每帧都发可以降低到每秒10-20次客户端进行插值平滑。压缩数据对于文本数据可以考虑使用GZip等压缩算法。对于二进制数据选择高效的序列化格式如Protobuf。差分更新只发送发生变化的数据而不是整个状态。例如只发送“位置变化了(1,0,0)”而不是“新位置是(10,5,3)”。对象池频繁创建和销毁字节数组、网络消息对象会产生GC垃圾回收压力使用对象池复用它们。6.2 调试技巧在Unity编辑器中模拟多设备可以运行多个编辑器实例或者使用命令行参数-batchmode -nographics启动多个无头模式实例进行测试。使用网络调试工具Wireshark抓包分析神器可以查看所有经过网卡的数据包验证UDP广播是否发出、TCP连接是否建立。NetCat (nc)在命令行快速创建一个TCP/UDP服务器或客户端用于测试你的Unity应用是否能与之通信。ADB Logcat查看安卓设备上Unity应用的详细日志定位崩溃和异常。在代码中增加详细的日志在不同阶段如开始监听、收到连接、收到数据、断开连接输出带时间戳和线程ID的日志便于追踪流程。7. 常见问题与排查实录在实际开发中你几乎一定会遇到下面这些问题。7.1 连接失败SocketException: No connection could be made检查防火墙确保电脑或安卓设备上的防火墙没有阻止你使用的端口如9050, 9051。在开发机上可以临时关闭防火墙测试。检查IP地址确保客户端连接的IP地址是服务器在局域网内的正确IP而不是127.0.0.1(本地环回) 或公网IP。使用ipconfig(Windows) 或ifconfig(macOS/Linux) 查看服务器的局域网IP。检查服务器是否已启动确认服务器的TcpListener.Start()已成功执行并且在监听。检查网络环境确保所有设备连接到同一个Wi-Fi网络或通过热点连接。有些公共Wi-Fi会启用“客户端隔离”阻止设备间互访。7.2 UDP广播收不到响应广播地址问题尝试将IPAddress.Broadcast(255.255.255.255) 替换为具体的子网广播地址例如你的IP是192.168.1.100子网掩码是255.255.255.0那么广播地址是192.168.1.255。路由器设置有些路由器或网络设备默认禁止UDP广播。尝试在家庭网络下测试企业网络可能有限制。权限问题在安卓上确保已添加CHANGE_WIFI_MULTICAST_STATE权限如果需要。多网卡问题服务器或客户端设备有多个网络接口如同时连了Wi-Fi和有线。确保你的Socket绑定到了正确的接口IPAddress.Any通常可以但有时需要指定。7.3 数据接收不完整或粘包这是TCP的固有特性TCP是流式协议没有消息边界。Read一次可能读到半条消息也可能读到一条半消息。必须实现“解包”逻辑如前文所述使用“长度前缀法”是最可靠的。在接收端先读取固定长度的头部如4字节的int得到后续数据体的长度然后精确读取该长度的字节这才是一条完整的消息。循环此过程。7.4 安卓设备作为服务器其他设备找不到获取的IP错误在安卓上Dns.GetHostEntry(Dns.GetHostName())方法可能无法返回正确的WLAN IP。更可靠的方法是遍历所有网络接口public string GetLocalIPAddress() { string localIP ; NetworkInterface[] interfaces NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface ni in interfaces) { // 筛选出启用、非回环、以太网或WLAN接口 if (ni.OperationalStatus OperationalStatus.Up ni.NetworkInterfaceType ! NetworkInterfaceType.Loopback (ni.NetworkInterfaceType NetworkInterfaceType.Wireless80211 || ni.NetworkInterfaceType NetworkInterfaceType.Ethernet)) { foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { if (ip.Address.AddressFamily AddressFamily.InterNetwork) { localIP ip.Address.ToString(); break; } } } if (!string.IsNullOrEmpty(localIP)) break; } return localIP; }热点模式如果一台安卓设备开热点另一台连接它它们处于一个隔离的小局域网。此时作为热点的设备其WLAN IP可能是一个固定的地址如192.168.43.1连接设备的IP则是192.168.43.x。需要确认获取的是这个热点网络的IP。7.5 应用切到后台后断连系统限制安卓系统会限制后台应用的网络活动以省电。解决方案使用前台服务在AndroidManifest.xml中声明FOREGROUND_SERVICE权限并在代码中启动一个前台服务关联一个常驻通知。使用WakeLock (谨慎)获取PARTIAL_WAKE_LOCK但务必在不需要时如断开连接后立即释放。设计重连机制应用从后台回到前台时主动检测连接状态并尝试重连。实现Unity安卓设备间的局域网通信就像在派对上帮一群只会说家乡话的人找到共同语言并开始聊天。核心在于选择合适的“通信协议”TCP/UDP/库作为语言设计好“发现机制”UDP广播让大家互相认识建立“可靠通道”TCP连接进行深入交流并处理好移动设备这个特殊“派对场地”的各种规矩权限、后台、省电。这个过程会踩不少坑从粘包拆包到线程安全从广播失效到IP获取错误每一个问题都需要耐心调试。但一旦跑通你将获得一个高性能、低延迟、不依赖外网的设备互联方案能为你的应用打开一片全新的场景。我个人的经验是先从最简单的TCP回显服务器和客户端练手再逐步加入UDP发现、协议设计、异常处理最后再考虑性能优化和平台适配这样步步为营更容易建立起对整个系统的掌控感。