做游戏联机,最容易踩的坑是什么?
很多人一想到“游戏要支持多人同时在线”,第一反应就是买云服务器、申请公网 IP、部署一套 WebSocket 服务端,再配 MySQL 和 Redis,最后还要处理 NAT 穿透、负载均衡、网络抖动。这套链路不是不对,而是绝大多数项目在玩法验证阶段根本走不到这一步,服务器账单却已经先来了。
《BigWalk》是一个以步行探索、社交互动为核心玩法的轻量级元宇宙项目。项目现阶段最需要的不是支撑十万人在线的云架构,而是先让三五个测试玩家在同一局域网里稳定地走动、对话、触发事件。说直白一点:先把局域网联机跑通,再谈公网和并发。
这篇文章会以《BigWalk》为例,给出一个可以落在本地的局域网联机完整方案,覆盖服务端监听、广播发现、客户端连接、防火墙放行、联机验证和常见排错。读完以后,你应该能在一台电脑上启动服务端,让同一局域网内另外几台电脑上的客户端稳定加入游戏,并且知道每一步为什么这样做、失败时先看哪里。
1. 为什么局域网联机是《BigWalk》阶段性的正确选择
1.1 联机机制不是附加功能,而是元宇宙的核心玩法
如果一款游戏叫“元宇宙”,但玩家在游戏里看不到任何人,那它和单机步行模拟器没有本质区别。联机机制在这个项目里不是做完了核心玩法之后再加的补丁,而是从一开始就要验证的核心体验。
《BigWalk》的基础联机需求可以拆成三块:
- 位置同步:玩家 A 移动时,玩家 B 的客户端要实时看到 A 的位置变化。
- 状态共享:场景里的门、箱子、NPC 交互结果,所有客户端要保持一致。
- 事件分发:A 触发了某个任务或特效,B 也要收到同样的广播。
这三块在局域网里实现,网络延迟通常在 1ms 到 5ms 之间,远低于公网常见的 30ms 到 100ms。对玩法验证来说,这是一个干扰最少的环境。
1.2 局域网联机的三个直接收益
第一,成本接近于零。不需要租服务器,不需要带宽费用,一台宿舍里的 Windows 电脑就可以当作服务端。
第二,调试效率高。客户端和服务端都在同一个物理网络里,日志可以并排看,断点可以同时打。服务端崩了,直接重启,没有远程运维的负担。
第三,逼迫你把“服务端边界”想清楚。很多人做联机游戏,喜欢把逻辑堆在客户端。但在局域网联机版本里,你必须要回答一个问题:哪些数据由服务端决定,哪些数据由客户端上报。这个边界一旦定清楚,后面迁移公网会非常顺。
1.3 什么时候不该用局域网联机
局域网不是万能的。如果《BigWalk》的测试玩家分布在不同的城市,那局域网方案完全不适用。另外,如果项目要验证的是“极端弱网下的表现”或“千人同屏的压力测试”,局域网环境也提供不了这种场景。
更稳妥的判断是:局域网联机是开发期和封闭测试期的手段,不是产品最终形态。它解决的问题是“把联机玩法先跑起来”,而不是“把架构一步做到生产级”。
2. 联机架构:P2P、独立服务端与局域网广播设计
2.1 P2P 房间制
P2P 模式里,一个玩家创建房间,其他玩家直接连接房主。房主的电脑既跑游戏,又承担同步逻辑。
优点是省一台服务器,适合两个人临时联机。缺点是房主的网络和机器性能会直接影响所有玩家,而且如果房主掉线,整局就崩了。
对《BigWalk》来说,P2P 适合快速原型验证,但不适合作为长期方案。因为元宇宙项目后续要加入任务系统、世界状态持久化,这些逻辑放在房间内运行的玩家设备上并不合理。
2.2 客户端-服务器模式
独立服务端模式中,有一个专门的进程负责维护世界状态、接收玩家输入、广播游戏事件。客户端只负责表现层和输入采集。
这是目前网络游戏的主流架构,也更容易迁移到公网。服务端写好了,后续无非是把进程部署到云主机,再把网络层从局域网换成公网协议,核心逻辑不需要推翻重来。
《BigWalk》局域网联机版本采用的就是这个方案,下文所有配置和代码都基于它展开。
2.3 局域网广播发现
服务端启动后,客户端怎么知道服务端的 IP 和端口?
最原始的方法是手动输入 IP。但局域网里 DHCP 会动态分配地址,今天服务端是 192.168.1.100,明天可能就变成了 192.168.1.105。每次联机都要先跑到服务端那台机器上看 IP,体验很差。
所以更合理的方案是让服务端启动时周期性地向局域网广播一条 UDP 消息,消息里包含游戏名、服务端名称、端口、当前玩家数等信息。客户端在局域网内监听广播消息,收到后自动把服务器列出来。
广播发现减少的是配置成本,它让“打开客户端就能看到局域网房间”成为可能。不过需要注意,UDP 广播默认只在同一网段内传播,如果两台电脑不在同一个 VLAN 或没有使用组网工具,广播是收不到的。
2.4 两种模式的核心对比
| 对比维度 | P2P 房间制 | 客户端-服务器模式 |
|---|---|---|
| 额外设备 | 不需要 | 需要一台常开电脑 |
| 逻辑权威 | 房主 | 服务端 |
| 房主掉线影响 | 全房间崩溃 | 客户端掉线不影响其他人 |
| 后续迁移公网 | 改造困难 | 服务端可直接部署 |
| 适合阶段 | 快速原型 | 正式联机验证 |
3. 环境准备:硬件、网络与项目目录规划
3.1 网络环境
最基础的局域网联机环境只需要一台路由器加两台电脑,两台电脑连接到同一个路由器,处于同一个网段。
需要确认以下几点:
- 两台电脑都能正常上网,并且能互相 ping 通。
- 路由器 DHCP 开启(默认是开启的)。
- 如果公司或学校网络开启了 AP 隔离,需要找管理员调整,否则设备之间无法互相访问。
- 无线网络可以用,但联机测试优先使用有线网络,可以排除无线丢包带来的干扰。
3.2 操作系统与运行时
本文示例在 Windows 10 / Windows 11 上运行。服务端示例使用 .NET 编写,因此需要安装 .NET SDK 或者 .NET Runtime,版本以你实际安装为准,文章重点演示通用思路。
之所以用 .NET 举例,是因为 Unity 客户端也是 C# 技术栈,服务端和客户端可以共享一部分数据结构和消息定义。即使你的《BigWalk》客户端使用别的引擎,只要理解了网络通信原理,换成 Go、Java、Node.js 服务端都只是语法替换的问题。
3.3 项目目录规划
BigWalk/ ├── Client/ # Unity 客户端 │ ├── Assets/ │ │ ├── Scripts/Network/ # 网络连接、广播发现脚本 │ │ └── Scenes/ # 游戏场景 │ └── ProjectSettings/ ├── Server/ # 独立服务端 │ ├── Config/ │ │ └── server_config.yaml │ ├── BigWalk.Server/ # 服务端源码 │ └── scripts/ # 启动脚本 ├── Tools/ # 联机测试工具 └── Docs/ # 联机部署文档把客户端和服务端拆开有两个好处:一是服务端不依赖 Unity 编辑器,启动更快;二是后续上公网时,服务端可以直接部署到 Linux 云主机,不需要跟随项目完整安装客户端。
4. 服务端搭建:配置文件、TCP 监听与 UDP 广播
4.1 服务端配置文件
服务端需要一个配置文件,把端口、玩家上限、广播开关等参数外置,避免每次修改都要重新编译。这里以server_config.yaml为例:
# Server/Config/server_config.yaml server: name: BigWalk-LAN-Server bind_ip: 0.0.0.0 port: 7777 max_players: 16 tick_rate: 30 broadcast: enabled: true port: 47777 interval_seconds: 2 world: name: default_world save_path: /data/bigwalk/worlds/default字段含义:
bind_ip:服务端监听地址。填0.0.0.0表示监听本机所有网卡,局域网内其他设备才能连进来。port:游戏 TCP 连接端口,客户端需要连到这个端口。tick_rate:服务端每秒处理逻辑的次数,类似“每秒刷新多少帧游戏状态”。broadcast.port:UDP 广播端口,客户端监听这个端口来发现服务器。
4.2 服务端主程序
下面是一个最小可运行的服务端示例,只做两件事:启动 TCP 监听接收玩家连接,启动 UDP 广播让客户端能发现它。
// Server/BigWalk.Server/Program.cs using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace BigWalk.Server { public static class Program { private static int _tcpPort = 7777; private static int _broadcastPort = 47777; public static async Task Main(string[] args) { // 简单解析命令行参数,实际项目通常从配置文件读取 ParseArgs(args); Console.WriteLine($"[BigWalk] Server starting... TCP port: {_tcpPort}"); var tcpListener = new TcpListener(IPAddress.Any, _tcpPort); tcpListener.Start(); Console.WriteLine($"[BigWalk] TCP listener started on port {_tcpPort}"); // 启动 UDP 广播线程 var broadcastCts = new CancellationTokenSource(); _ = Task.Run(() => BroadcastLoop(broadcastCts.Token)); // 接收玩家连接的循环 while (true) { var client = await tcpListener.AcceptTcpClientAsync(); _ = HandleClient(client); } } private static async Task HandleClient(TcpClient client) { var remote = client.Client.RemoteEndPoint?.ToString(); Console.WriteLine($"[BigWalk] Player connected: {remote}"); try { using var stream = client.GetStream(); var buffer = new byte[2048]; int readCount = await stream.ReadAsync(buffer, 0, buffer.Length); if (readCount > 0) { var message = Encoding.UTF8.GetString(buffer, 0, readCount); Console.WriteLine($"[BigWalk] Received first message: {message}"); } } catch (Exception ex) { Console.WriteLine($"[BigWalk] Client error: {ex.Message}"); } finally { client.Close(); } } private static async Task BroadcastLoop(CancellationToken token) { using var udp = new UdpClient(); udp.EnableBroadcast = true; var broadcastAddr = new IPEndPoint(IPAddress.Broadcast, _broadcastPort); while (!token.IsCancellationRequested) { var json = "{\"game\":\"BigWalk\",\"server\":\"BigWalk-LAN-Server\",\"port\":7777,\"players\":0,\"max_players\":16}"; var bytes = Encoding.UTF8.GetBytes(json); await udp.SendAsync(bytes, bytes.Length, broadcastAddr); Console.WriteLine($"[BigWalk] Broadcast sent to {broadcastAddr}"); await Task.Delay(TimeSpan.FromSeconds(2), token); } } private static void ParseArgs(string[] args) { for (int i = 0; i < args.Length; i++) { if (args[i] == "--port" && i + 1 < args.Length) { _tcpPort = int.Parse(args[i + 1]); } else if (args[i] == "--broadcast-port" && i + 1 < args.Length) { _broadcastPort = int.Parse(args[i + 1]); } } } } }这个示例看起来简单,但已经覆盖了局域网联机服务端的两个核心能力:连接接入和信息广播。真正进入游戏逻辑后,你会在HandleClient里扩展位置同步、状态处理、消息编解码,但网络骨架不会变。
4.3 编译与启动服务端
在项目根目录执行:
cd Server/BigWalk.Server dotnet run启动成功后,日志里会出现TCP listener started on port 7777,随后每两秒输出一条Broadcast sent。如果能看到这两句,服务端的网络基础层已经通了。
5. 客户端接入:固定 IP 直连、广播发现与连接状态管理
5.1 固定 IP 直连
最直接的接入方式是在客户端输入服务端电脑的局域网 IP 和端口。先在服务端电脑上执行ipconfig获取 IP:
ipconfig重点看“无线局域网适配器”或“以太网适配器”下的 IPv4 地址,通常是192.168.x.x。把这个 IP 填到客户端的连接面板里,端口填7777。
好处是省事,适合两个人临时联机。缺点前面说过,IP 可能变化,不适合常态化使用。
5.2 客户端连接代码
以下是 Unity 客户端中一个最小连接示例,使用 C# 的TcpClient连接服务端并发送一条 JSON 消息:
// Client/Assets/Scripts/Network/BigWalkClient.cs using System; using System.Net.Sockets; using System.Text; using UnityEngine; public class BigWalkClient : MonoBehaviour { private TcpClient _client; private NetworkStream _stream; public void ConnectToServer(string ip, int port) { try { _client = new TcpClient(); _client.Connect(ip, port); _stream = _client.GetStream(); var joinMessage = "{\"type\":\"join\",\"playerName\":\"Player1\",\"x\":0,\"y\":0}"; var bytes = Encoding.UTF8.GetBytes(joinMessage); _stream.Write(bytes, 0, bytes.Length); Debug.Log($"[BigWalkClient] Connected to {ip}:{port}"); } catch (SocketException ex) { Debug.LogError($"[BigWalkClient] Connection failed: {ex.Message}"); } } void OnDestroy() { _client?.Close(); } }这里真正容易踩坑的地方是:服务端监听的是0.0.0.0,客户端连接时不能填0.0.0.0,必须填服务端电脑的实际局域网 IP。很多第一次联机的人在这里卡住,以为服务端绑定了所有网卡,客户端就能用任意地址连,实际上客户端的目标地址必须是可达的具体 IP。
5.3 UDP 广播发现
为了让客户端自动发现局域网内的《BigWalk》服务器,客户端可以启动一个 UDP 监听器,接收服务端发来的广播消息。
// Client/Assets/Scripts/Network/BigWalkDiscovery.cs using System; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; public class BigWalkDiscovery : MonoBehaviour { private UdpClient _udpClient; private bool _running; void Start() { _udpClient = new UdpClient(47777); _udpClient.EnableBroadcast = true; _running = true; _udpClient.BeginReceive(new AsyncCallback(OnReceive), null); } private void OnReceive(IAsyncResult ar) { if (!_running) return; try { var remote = new IPEndPoint(IPAddress.Any, 0); var bytes = _udpClient.EndReceive(ar, ref remote); var message = Encoding.UTF8.GetString(bytes); Debug.Log($"[BigWalkDiscovery] Found server from {remote.Address}: {message}"); // 这里可以把服务器信息加入 UI 列表 _udpClient.BeginReceive(new AsyncCallback(OnReceive), null); } catch (Exception ex) { Debug.LogError($"[BigWalkDiscovery] Receive error: {ex.Message}"); } } void OnDestroy() { _running = false; _udpClient?.Close(); } }需要提醒的是,在 Unity 编辑器中测试 UDP 监听时,Windows 防火墙通常会弹出首次访问提示,必须勾选“专用网络”并允许访问,否则广播消息会被系统防火墙静默丢弃。
6. 防火墙放行与局域网连通性调试
6.1 放行 TCP 游戏端口和 UDP 广播端口
即使代码没有写错,Windows 防火墙也经常成为联机失败的“隐形杀手”。最稳妥的方式是手动添加入站规则,提前放行服务端需要监听的端口。
在管理员 PowerShell 中执行:
New-NetFirewallRule -DisplayName "BigWalk TCP 7777" -Direction Inbound -Protocol TCP -LocalPort 7777 -Action Allow New-NetFirewallRule -DisplayName "BigWalk UDP 47777" -Direction Inbound -Protocol UDP -LocalPort 47777 -Action Allow注意,这两条规则只需要在服务端电脑上执行。客户端通常不需要额外放行,但如果客户端要监听 UDP 端口做广播发现,也需要在客户端机器上放行 UDP 47777 入站。
6.2 网络连通性检查
如果客户端还是连不上,按顺序做以下检查:
第一步,确认两台电脑能互相 ping 通:
ping 192.168.1.100如果 ping 不通,先检查是否在同一网段、路由器是否开启了 AP 隔离。
第二步,确认服务端端口正在监听:
netstat -ano | findstr 7777如果没有输出,说明服务端没有启动成功,或者监听端口被其他程序占用。
第三步,从客户端机器测试 TCP 端口连通性:
Test-NetConnection 192.168.1.100 -Port 7777输出中TcpTestSucceeded : True才是正常状态。如果这里显示 False,基本可以确定是防火墙拦截或服务端没启动。
6.3 虚拟局域网组网工具
如果测试玩家不在同一个物理网络,但又想使用局域网联机方案,可以使用虚拟局域网组网工具,比如 Radmin LAN。这类工具会把分布在不同地点的多台设备通过加密隧道组合成一个虚拟局域网,设备之间获得类似局域网的访问体验。
这类工具适合小范围朋友联机测试,也适合《BigWalk》在多地区小规模内测时使用。但需要记住:虚拟局域网依然依赖公网链路,实际延迟会明显高于物理局域网,不要把它当成延迟优化手段。
7. 联机验证与性能观察
7.1 启动顺序
正确的启动顺序是:先启动服务端,再启动客户端。如果你先开了客户端,客户端广播发现阶段会一直收不到服务器信息,直到服务端上线后下一个广播周期到来才会发现。
推荐流程:
- 在服务端电脑启动
dotnet run。 - 看到
TCP listener started on port 7777后,启动 Unity 客户端。 - 客户端启动后,进入联机界面,等待自动发现服务器,或手动输入服务端 IP。
- 点击连接后,观察服务端控制台是否打印
Player connected。
7.2 预期日志
服务端正常时,日志大致如下:
[BigWalk] Server starting... TCP port: 7777 [BigWalk] TCP listener started on port 7777 [BigWalk] Broadcast sent to 255.255.255.255:47777 [BigWalk] Broadcast sent to 255.255.255.255:47777 [BigWalk] Player connected: 192.168.1.102:54321 [BigWalk] Received first message: {"type":"join","playerName":"Player1","x":0,"y":0}看到Player connected,说明 TCP 连接已经建立;看到Received first message,说明客户端和服务端的消息格式匹配上了。
7.3 需要观察的指标
联机运行中,重点看三个指标:
- 连接稳定性:连接是否频繁掉线。频繁断开先看有没有路由器 NAT 表溢出,再看服务端日志有没有异常。
- 消息延迟:从玩家 A 发送位置消息到玩家 B 看到位置变化的时间差。局域网环境应该在几十毫秒以内。
- 丢包率:执行
ping 192.168.1.100 -t观察。丢包率超过 1% 时,优先检查无线网络信号强度和信道干扰。
7.4 单机多开测试
如果暂时没有多台电脑,也可以在一台电脑上启动一个服务端进程和两个客户端窗口进行联机测试。Unity 编辑器可以开多个实例,但要确保每个实例使用不同的玩家数据目录,否则会出现配置冲突。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 客户端连不上服务端 | 服务端未启动或端口被占用 | netstat -ano | findstr 7777 | 启动服务端,或修改配置端口 |
| 能 ping 通但连接失败 | 防火墙拦截入站 TCP | Test-NetConnection IP -Port 7777 | 添加入站防火墙规则 |
| 客户端看不到局域网服务器 | UDP 广播未到达客户端 | 检查广播端口是否一致 | 放行 UDP 47777,或改用手动 IP 连接 |
| 广播发现列表为空 | 两台设备不在同一网段 | ipconfig对比网段 | 调整网络,或使用组网工具 |
| 连接后立刻断开 | 服务端收到非法消息并异常关闭 | 查看服务端异常堆栈 | 统一消息格式,增加异常捕获 |
| 游戏内延迟突然升高 | 无线网络干扰或后台占用 | ping持续观察丢失 | 换有线网络,关闭后台下载 |
| 重启后 IP 变了 | DHCP 动态分配 | ipconfig查看当前 IP | 在路由器中配置 DHCP 静态绑定 |
| 第一次运行被防火墙拦截 | 系统安全策略弹出拦截 | 检查防火墙入站规则 | 提前在管理员 PowerShell 中配置 |
这里最容易忽视的是 UDP 广播问题。TCP 连接是显式的,连不上时所有人都能意识到网络层有故障。但 UDP 广播是“发了就忘”的模型,服务端以为自己在广播,客户端可能根本没收到,而且两边都不会有报错。
遇到“广播发现列表为空”时,最快的解决办法是先用固定 IP 直连验证 TCP 链路,再回头排查 UDP 广播,避免同时面对两个不确定项。
9. 安全边界、工程化建议与下一步实践
9.1 局域网也不是绝对安全
很多开发者觉得“局域网”等于“安全”,这个认知需要被修正。只要一台设备接入同一个局域网,它就有能力进行端口扫描和流量监听。虽然《BigWalk》现阶段只是玩法验证,但网络层的基本安全意识必须建立。
在实际项目中,至少要做到三点:
第一,不要把服务端绑定到0.0.0.0再暴露到不可信网络。联机测试结束后,关闭服务端进程,不要让它长期挂在后台。
第二,为每个进入游戏的玩家增加简单的身份标识,比如客户端生成的 token。当前示例里只发了一个玩家名,这只能用来做演示,生产环境需要服务端校验身份。
第三,后续如果加入玩家消息广播,消息内容要做最大长度限制,防止恶意客户端向服务端发送超大消息拖垮进程。
9.2 工程化建议
- 配置外置:端口、玩家上限、广播间隔都从配置文件读取,不要硬编码在代码里。
- 日志分级:服务端日志至少区分 Debug、Info、Error 三级,联机问题定位时,Error 日志要能直接指出哪个玩家、哪个会话、哪个方法出了异常。
- 消息格式统一:尽早把 JSON 消息体抽象成消息类,不要用散落的字符串拼接。
- 自动化测试:可以写一个没有界面的一体化测试客户端,模拟 10 个玩家同时连接,验证服务端在预期负载下是否稳定。
- 版本管理:客户端和服务端的协议版本号要单独管理,避免新旧客户端混用导致解析错误。
9.3 从局域网迈向公网
当《BigWalk》的玩法验证完成,需要支持异地玩家联机时,迁移路径其实是现成的。客户端-服务器架构下,服务端可以直接部署到云主机,只需把网络层从局域网广播改为公网地址或云服务发现。
另一个低成本路线是虚拟局域网组网,它不改变代码,但受限于公网链路质量,适合小规模封闭测试。真正面向大众时,还是应该走完整的上线流程:云服务器、负载均衡、数据库持久化、日志监控、灰度发布。
9.4 下一步实践建议
如果你正在开发类似《BigWalk》的联机游戏,下一步建议按顺序做三件事:
一,把网络连接代码和游戏逻辑分层。连接层只负责收发字节流,游戏逻辑层只处理消息对象,二者不要混在一起。
二,用广播发现加固定 IP 直连两种模式并行开发。一个是玩家体验的加分项,一个是排查问题的保底手段。
三,尽早做断线重连。局域网联机虽然稳定,但无线网卡休眠、路由器重启等现实问题依然存在。断线重连不是生产环境才需要的能力,测试期就需要暴露和修复。
局域网联机不是终点,它是走向更复杂网络架构前最值得投入的第一个里程碑。把这一层跑稳,《BigWalk》后面的公网联机之路会顺畅得多。