【Pygame】第19章 网络多人游戏基础与局域网联机原理

摘要

网络多人游戏是现代游戏的重要组成部分,它允许多个玩家在不同设备上通过网络共同参与同一局游戏,并在实时交互中形成更丰富的玩法体验。

与单机游戏相比,网络游戏不仅要处理角色移动、攻击、聊天和房间管理等逻辑,还要面对网络延迟、数据丢包、乱序到达、同步冲突和断线重连等问题。因此,网络编程既是玩法系统的一部分,也是保证游戏可玩性和稳定性的基础工程。

本章将系统介绍网络编程的基础知识,重点讲解 TCP 和 UDP 协议的区别、客户端与服务器架构的设计方法,以及状态同步和输入同步的基本原理。我们还会进一步说明局域网联机的搭建思路,包括服务器地址选择、端口监听、IP 分配、同一子网通信和防火墙配置等内容,帮助读者理解多人游戏如何在实际网络环境中运行。最后,本章将展示如何使用 GPT-5.4 来生成网络游戏代码。由于国内无法访问 OpenAI 官网,因此使用国内镜像站可以合法注册使用 GPT-5.4 最新模型。翻墙行为违反中国法律法规,请大家遵守法律,不要翻墙。国内镜像站提供了稳定、合法的 AI 服务访问渠道,完全能够满足学习和开发需求。

注册入口:AIGCBAR 镜像站

API 站注册入口:API 独立站

通过本章的学习,读者将能够理解网络游戏的基本原理,并初步掌握局域网多人联机系统的实现方法。


19.1 网络游戏为什么需要专门的通信机制

网络游戏和本地游戏最大的区别,在于"数据是否需要跨设备同步"。

单机游戏只需要在本机内维护玩家状态,而网络游戏必须把一台机器上的操作结果传递给另一台机器,甚至多台机器同时共享同一份游戏状态。

这就引出了网络通信中的两个核心目标:

  1. 把玩家输入传递给其他设备
  2. 把游戏状态同步到所有客户端

如果把一局网络游戏抽象为一个系统,那么它可以表示为:

G = f ( I , S , N ) G = f\left(I, S, N\right) G=f(I,S,N)

其中:

  • ( I ) 表示玩家输入
  • ( S ) 表示游戏状态
  • ( N ) 表示网络通信过程

网络游戏的复杂性,就在于这三个部分并不是独立的,而是相互影响、相互制约的。

输入越频繁,通信压力越大;状态越复杂,同步成本越高;网络越不稳定,玩家体验越容易受到影响。


19.2 TCP 和 UDP 的基础理论

在网络编程中,最常见的两种传输协议就是 TCP 和 UDP。

它们都属于传输层协议,但设计目标完全不同。

理解它们的区别,是设计多人游戏网络架构的第一步。

19.2.1 TCP 的特点

TCP 叫作传输控制协议,它强调的是可靠性

当数据通过 TCP 发送时,系统会尽量保证:

  • 数据不会丢
  • 数据按顺序到达
  • 数据如果出错可以重传
  • 收发双方都能确认连接状态

换句话说,TCP 更像一种"可靠投递"机制。

如果把发送的数据包记为 ( P ),那么 TCP 的目标可以理解为:

P s e n t → P r e c e i v e d P_{sent} \rightarrow P_{received} Psent→Preceived

并且尽量保证顺序一致:

P 1 , P 2 , P 3 → P 1 , P 2 , P 3 P_1, P_2, P_3 \rightarrow P_1, P_2, P_3 P1,P2,P3→P1,P2,P3

这意味着 TCP 非常适合:

  • 登录验证
  • 聊天消息
  • 存档同步
  • 房间创建与加入
  • 重要状态通知

但 TCP 也有一个明显缺点:延迟相对较高

因为它要处理确认、重传、排序和拥塞控制,所以在实时性要求极高的场景中,可能不如 UDP 灵活。


19.2.2 UDP 的特点

UDP 叫作用户数据报协议,它强调的是速度和简洁

UDP 发送数据时不建立复杂连接,也不保证数据一定送达。

它只负责尽快把数据发出去,至于是否丢失、是否乱序、是否重复,则由应用层自己处理。

如果把 UDP 发送的数据记为 ( Q ),那么它的行为更像:

Q s e n t ↛ Q g u a r a n t e e d Q_{sent} \nrightarrow Q_{guaranteed} Qsent↛Qguaranteed

也就是说,UDP 不提供可靠到达保证。

但正因为它机制简单、开销小,所以延迟更低,更适合实时交互。

UDP 特别适合:

  • 玩家位置同步
  • 角色朝向同步
  • 频繁移动数据
  • 实时动作游戏
  • 射击类游戏中的高频状态广播

19.3 TCP 和 UDP 的对比分析

两者的差异可以从多个角度理解。

特性 TCP UDP
可靠性 可靠传输 可能丢包
顺序 保证顺序 不保证
延迟 较高 较低
开销 较大 较小
连接方式 面向连接 无连接
适用场景 聊天、登录、存档 实时位置、动作同步

从游戏开发角度看,TCP 和 UDP 并不是谁绝对更好,而是"谁更适合当前需求"。

例如:

  • 主菜单登录适合 TCP
  • 游戏过程中的位置广播适合 UDP
  • 聊天系统适合 TCP
  • 射击命中判定有时会结合两者

因此,很多商业游戏会采用混合通信方案

关键数据走 TCP,实时数据走 UDP。


19.4 客户端与服务器架构

多人游戏最常见的结构是客户端与服务器架构。

在这种架构中,服务器负责管理游戏的核心规则和状态,客户端负责收集输入并展示画面。

也就是说,客户端更多承担"表现层",服务器更多承担"权威层"。

19.4.1 为什么要有服务器

如果所有玩家都直接互相通信,那么系统会迅速变得混乱。

比如 A 需要知道 B 的位置,B 又要知道 C 的位置,C 还要知道 D 的状态。

当玩家数量上升时,连接关系会快速膨胀。

如果玩家数量为 ( n ),完全互联的连接数可以表示为:

L = n ( n − 1 ) 2 L = \frac{n\left(n - 1\right)}{2} L=2n(n−1)

这个公式说明,玩家越多,直接点对点通信的复杂度就越高。

因此,通常采用服务器集中处理的方式,让所有客户端都向服务器汇报,再由服务器统一转发或计算。


19.4.2 服务器的职责

在标准架构中,服务器通常负责:

  • 接收客户端数据
  • 校验输入是否合法
  • 维护游戏状态
  • 广播同步结果
  • 处理房间和玩家管理
  • 做冲突裁决

这种模式下,服务器是"裁判",客户端是"参赛者"。

这样做的好处是逻辑集中、规则统一、作弊更难、同步更清晰。


19.4.3 客户端的职责

客户端通常负责:

  • 接收玩家输入
  • 发送输入或状态给服务器
  • 接收服务器下发的数据
  • 渲染画面
  • 播放音效和动画
  • 展示 UI

客户端一般不直接决定最终规则,而是根据服务器结果进行表现。

这样可以避免各个设备之间因为逻辑不一致而产生冲突。


19.5 局域网联机的基础原理

局域网联机是理解多人游戏最好的起点。

因为它不需要复杂的公网穿透、云服务器或 NAT 映射,只需要让几台设备处于同一个局域网环境中,就可以完成通信。

19.5.1 什么是局域网

局域网,也就是 LAN,指的是在较小范围内连接起来的一组设备,例如:

  • 家庭路由器下的电脑
  • 教室里的电脑
  • 公司内部网络
  • 同一个交换机下的测试机器

只要这些设备在同一个局域网中,它们通常就可以通过本地 IP 地址相互访问。

例如:

  • 服务器 IP:192.168.1.10
  • 客户端 IP:192.168.1.20

只要端口开放且网络允许,就可以直接通信。


19.5.2 局域网联机的基本流程

局域网联机一般分为以下几个步骤:

  1. 一台设备作为服务器
  2. 服务器绑定本地 IP 和端口
  3. 其他设备作为客户端
  4. 客户端连接服务器的局域网 IP
  5. 双方开始收发数据

如果把服务器监听过程写成概念模型,可以表示为:

Bind ( I P , p o r t ) → Listen → Accept \text{Bind}\left(IP, port\right) \rightarrow \text{Listen} \rightarrow \text{Accept} Bind(IP,port)→Listen→Accept

客户端的连接过程则可以表示为:

Connect ( I P , p o r t ) → Handshake → DataExchange \text{Connect}\left(IP, port\right) \rightarrow \text{Handshake} \rightarrow \text{DataExchange} Connect(IP,port)→Handshake→DataExchange


19.5.3 如何确认局域网 IP

在局域网联机中,最重要的是知道服务器设备的局域网 IP。

常见方式包括:

  • Windows 下查看网络适配器信息
  • 使用 ipconfig 命令
  • 在路由器管理页面中查看设备列表
  • 在编程中读取当前网卡地址

通常,局域网地址会以 192.168.x.x10.x.x.x172.16.x.x 这类形式出现。

客户端需要填写的就是这个服务器地址,而不是 127.0.0.1

因为 127.0.0.1 只表示本机回环地址,只能自己访问自己,不能让别的设备连接。


19.5.4 局域网联机为什么通常更稳定

局域网通信之所以常用于教学和原型开发,是因为它具有以下优势:

  • 延迟低
  • 带宽充足
  • 丢包少
  • 不需要复杂的公网配置
  • 适合快速测试联机逻辑

如果用延迟 ( d ) 表示网络往返时间,那么局域网通常满足:

d L A N ≪ d I n t e r n e t d_{LAN} \ll d_{Internet} dLAN≪dInternet

这意味着在局域网中,玩家之间的数据同步更容易稳定,也更适合用于调试实时交互逻辑。


19.6 基本网络同步方式

多人游戏中最重要的问题之一,就是"大家看到的内容是否一致"。

这就涉及同步机制。

常见的同步方式主要有两类:状态同步输入同步


19.6.1 状态同步

状态同步的核心思想是:

服务器直接把游戏当前状态发给客户端,客户端按这个状态进行显示。

例如服务器告诉客户端:

  • 玩家 A 在位置 ( x = 100, y = 200 )
  • 玩家 B 在位置 ( x = 300, y = 180 )

客户端接收到后直接更新画面。

这种方式简单直观,非常适合入门和小型项目。

如果状态向量记为 ( S ),则状态同步可表示为:

S s e r v e r → S c l i e n t S_{server} \rightarrow S_{client} Sserver→Sclient

优点是实现容易,缺点是如果网络延迟较高,画面可能不够平滑。


19.6.2 输入同步

输入同步的核心思想是:

客户端不直接上传最终结果,而是上传玩家输入,例如按键、方向、技能触发等。

服务器根据这些输入自己计算结果,再把结果发送给所有客户端。

如果输入序列记为 ( I ),游戏状态更新函数记为 ( F ),则可以写成:

S t + 1 = F ( S t , I t ) S_{t+1} = F\left(S_t, I_t\right) St+1=F(St,It)

这种方式的优势是服务器更权威,也更容易保证规则一致。

但是实现难度比状态同步更高,对同步精度和延迟控制要求也更严格。


19.7 延迟、抖动、丢包与乱序

在网络游戏中,数据到达时间并不总是稳定的。

这会产生几种典型问题:

19.7.1 延迟

数据从发送到接收需要时间。

这个时间越长,玩家感觉到的操作反馈就越慢。

19.7.2 抖动

即使平均延迟不高,每个包到达时间也可能忽快忽慢。

这种波动就叫抖动。

19.7.3 丢包

部分数据包可能在传输途中丢失。

UDP 中这种情况更常见。

19.7.4 乱序

数据包到达顺序和发送顺序不一致。

如果不处理,可能导致角色位置回跳或者动画错乱。

因此,网络游戏不能简单地"发过去就完了",还必须考虑:

  • 插值
  • 外推
  • 缓冲
  • 重发
  • 序列号
  • 时间戳

这些机制是多人游戏流畅运行的重要基础。


19.8 局域网联机的典型搭建方式

如果要在局域网中搭建一个简单的多人游戏原型,一般可以按下面的思路进行:

  1. 一台电脑作为服务器
  2. 服务器程序监听固定端口,例如 5555
  3. 其他电脑连接服务器局域网地址
  4. 服务器保存所有玩家状态
  5. 客户端定时发送输入或位置
  6. 服务器统一广播最新状态

这种方案适合教学、实验和原型验证。

它不要求复杂的外部服务,也不要求额外的云部署,非常适合学习网络游戏的基本架构。


19.9 使用 Python socket 创建简单联机程序

Python 的 socket 模块是实现网络通信最基础的工具之一。

它可以直接创建 TCP 或 UDP 连接,适合做入门级网络游戏实验。

下面是一个较完整的 TCP 局域网联机示例。

服务器负责接收客户端位置并广播所有玩家状态,客户端负责发送本地位置并显示其他玩家。

服务器代码

python 复制代码
import socket
import threading
import json

class GameServer:
    def __init__(self, host='0.0.0.0', port=5555):
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.bind((host, port))
        self.server.listen(4)

        self.clients = {}
        self.players = {}
        self.lock = threading.Lock()

    def handle_client(self, conn, addr):
        player_id = str(addr[1])

        with self.lock:
            self.clients[player_id] = conn
            self.players[player_id] = {"x": 100, "y": 100}

        print(f"玩家 {player_id} 加入")

        try:
            while True:
                data = conn.recv(1024)
                if not data:
                    break

                message = json.loads(data.decode())

                if "x" in message and "y" in message:
                    with self.lock:
                        self.players[player_id]["x"] = message["x"]
                        self.players[player_id]["y"] = message["y"]

                self.broadcast({
                    "type": "update",
                    "player_id": player_id,
                    "players": self.players
                }, exclude=player_id)

        except:
            pass
        finally:
            with self.lock:
                if player_id in self.clients:
                    del self.clients[player_id]
                if player_id in self.players:
                    del self.players[player_id]
            conn.close()
            print(f"玩家 {player_id} 离开")

    def broadcast(self, message, exclude=None):
        data = json.dumps(message).encode()
        with self.lock:
            for pid, conn in list(self.clients.items()):
                if pid != exclude:
                    try:
                        conn.send(data)
                    except:
                        pass

    def start(self):
        print("服务器启动,等待连接...")
        while True:
            conn, addr = self.server.accept()
            thread = threading.Thread(target=self.handle_client, args=(conn, addr))
            thread.start()

if __name__ == "__main__":
    server = GameServer()
    server.start()

客户端代码

python 复制代码
import pygame
import socket
import json
import threading

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

class NetworkClient:
    def __init__(self, host='127.0.0.1', port=5555):
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.client.connect((host, port))
        self.players = {}
        self.lock = threading.Lock()
        self.running = True

        self.receive_thread = threading.Thread(target=self.receive)
        self.receive_thread.start()

    def receive(self):
        while self.running:
            try:
                data = self.client.recv(1024)
                if data:
                    message = json.loads(data.decode())
                    if message["type"] == "update":
                        with self.lock:
                            self.players = message["players"]
            except:
                break

    def send_position(self, x, y):
        message = {"x": x, "y": y}
        try:
            self.client.send(json.dumps(message).encode())
        except:
            pass

    def close(self):
        self.running = False
        self.client.close()

client = NetworkClient()

player_x, player_y = 100, 100
player_speed = 5

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player_x -= player_speed
    if keys[pygame.K_RIGHT]:
        player_x += player_speed
    if keys[pygame.K_UP]:
        player_y -= player_speed
    if keys[pygame.K_DOWN]:
        player_y += player_speed

    client.send_position(player_x, player_y)

    screen.fill((50, 50, 50))

    with client.lock:
        for pid, player in client.players.items():
            x = player["x"]
            y = player["y"]
            color = (0, 255, 0) if pid == list(client.players.keys())[0] else (255, 0, 0)
            pygame.draw.circle(screen, color, (x, y), 20)

    pygame.display.flip()
    clock.tick(60)

client.close()
pygame.quit()

19.10 局域网联机时的常见注意事项

在局域网搭建多人游戏时,还需要注意以下问题:

1. 服务器地址不能写错

客户端必须连接服务器的局域网 IP,而不是本机回环地址。

2. 端口必须一致

服务器监听的端口和客户端连接的端口必须一致,例如都使用 5555

3. 防火墙可能拦截连接

如果连接失败,首先检查系统防火墙是否阻止了程序通信。

4. 数据包格式必须统一

客户端与服务器之间必须约定统一的消息格式,否则无法正确解析。

5. 不要频繁发送过大数据

过于频繁地发送完整状态会增加网络压力,应该尽量只发送必要内容。

如果把每秒发送的数据量记为 ( B ),那么在设计时就要注意让:

B a c t u a l ≤ B l i m i t B_{actual} \leq B_{limit} Bactual≤Blimit

否则就容易出现卡顿和同步延迟。


19.11 使用 GPT-5.4 生成网络代码

在网络游戏开发中,通信协议设计、同步逻辑、断线处理和重连机制往往比较复杂。

这类内容适合先让 GPT-5.4 生成基础框架,再由开发者结合项目需求进行调整。

下面是一个适合生成网络游戏代码的提示词块:

text 复制代码
请用 Python 实现一个完整的网络游戏框架,要求:

1. 使用 UDP 协议实现低延迟通信
2. 实现客户端预测和服务器回滚
3. 支持断线重连
4. 实现简单的聊天功能
5. 代码包含详细中文注释
6. 适合局域网联机测试
7. 支持房间创建和加入
8. 结构清晰,便于后续扩展

如果你希望更适合教学,还可以加入:

text 复制代码
额外要求:
1. 先实现 TCP 版本作为基础示例
2. 再说明如何切换到 UDP
3. 详细讲解消息格式设计
4. 演示客户端与服务器的通信流程

19.12 本章总结

本章介绍了网络多人游戏的基础知识,重点讲解了 TCP 和 UDP 协议的特点、客户端与服务器架构的设计思路,以及局域网联机的基本原理。

我们知道了 TCP 更适合可靠传输和重要消息,UDP 更适合实时同步和低延迟交互;也理解了客户端负责输入和渲染,服务器负责权威判断和状态管理。

对于初学者来说,最好的起点往往不是直接上复杂的公网联机,而是先在局域网环境中搭建一个简单的可运行原型。

在这个过程中,你会逐渐理解连接建立、消息发送、状态同步和网络调试的基本流程。

这些知识将为后续学习更高级的同步算法、预测机制和多人对战系统打下坚实基础。

本章知识点回顾

知识点 主要内容
网络协议 TCP、UDP 的区别与用途
架构设计 客户端与服务器模型
局域网联机 本地 IP、端口、连接流程
数据同步 状态同步、输入同步
网络问题 延迟、抖动、丢包、乱序

课后练习

  1. 实现 UDP 版本的聊天系统。
  2. 创建简单的多人射击游戏。
  3. 实现客户端预测。
  4. 使用 GPT-5.4 生成一个完整的多人游戏框架。
  5. 研究帧同步技术。

下章预告

在下一章中,我们将开始项目实战,实现一个完整的贪吃蛇游戏。

相关推荐
mounter6252 小时前
深度解析 Linux 内核 devlink:从硬件控制到跨功能速率调度的演进
linux·运维·服务器·网络·内核
MarsBighead2 小时前
VSCode Python 调试故障排查:`justMyCode` 配置项引发的血案
ide·vscode·python
中科三方2 小时前
HTTP劫持与DNS劫持有什么区别?如何识别和防范?
网络·网络协议·http·dns
迷藏4942 小时前
**发散创新:基于Python与深度学习的情绪识别实战全流程解析**在人工智能快速发展的今天,**情绪识别(Emoti
java·人工智能·python·深度学习
福尔摩斯张2 小时前
一文搞懂74HC595芯片(附详细使用方法)
linux·服务器·网络·单片机·嵌入式硬件
羊小猪~~2 小时前
LLM--SFT简介
python·考研·算法·ai·大模型·llm·微调
智算菩萨2 小时前
【Pygame】第16章 游戏存档系统设计与数据持久化实现
jvm·游戏·pygame
无心水2 小时前
17、Java内存溢出(OOM)避坑指南:三个典型案例深度解析
java·开发语言·后端·python·架构·java.time·java时间处理
admin and root2 小时前
XSS之Flash弹窗钓鱼
前端·网络·安全·web安全·渗透测试·xss·src