type
status
date
slug
summary
tags
category
icon
password
fullWidth
fullWidth
服务端Github传送门:Shirakoko/CrossChessServer: 井字棋小游戏联网模块的服务端
客户端Github传送门:Shirakoko/CrossChessClient: 技术策划笔试
服务端部分
服务端是.NET控制台应用程序。
1.使用说明
运行控制台应用
- 开发环境(Visual Studio)中运行:
- 打开项目后,点击工具栏中的绿色箭头(启动按钮)或按
F5
键运行程序。 - 程序将以调试模式启动,控制台窗口会显示
Hello, World!
,并等待用户输入命令。
- 生产环境(可执行文件)运行:
- 在项目生成目录(如
bin\Release\netx.x
)中找到生成的exe
文件。 - 双击
exe
文件运行程序,控制台窗口会显示Hello, World!
,并等待用户输入命令。
命令行输入操作
程序启动后,可以通过命令行输入以下命令来操作服务器:
- 启动服务器:
- 输入命令:
Start [--ip <IP地址>] [--port <端口号>] [--maxNum <最大客户端数量>]
- 示例:
Start
:使用默认IP地址(192.168.1.5
)、默认端口号(8080
)和默认最大客户端数量(10
)启动服务器。Start --ip 127.0.0.1 --port 8888 --maxNum 5
:使用IP地址127.0.0.1
、端口号8888
和最大客户端数量5
启动服务器。- 成功启动后,控制台会显示:
服务器开启成功,IP地址: <IP地址>,端口:<端口号>
。
- 关闭服务器:
- 输入命令:
Quit
- 成功关闭后,控制台会显示:
服务器关闭
。
- 其他输入:
- 如果输入的命令无效,程序会忽略并继续等待下一个有效命令
2.功能拆解
连接管理
连接管理模块负责处理客户端的连接和断开,确保服务器的稳定运行。具体功能包括:
- 客户端连接:
- 当客户端连接到服务器时,服务器会为其分配一个唯一的
clientID
,并将其加入客户端字典clientDict
。 - 服务器会开启心跳检测线程,定期检查客户端是否在线。
- 客户端断开:
- 当客户端断开连接时,服务器会将其从客户端字典
clientDict
和大厅用户字典hallClientDict
中移除。 - 服务器会通知所有在线玩家更新大厅用户列表。
- 心跳检测:
- 服务器会定期检查客户端的心跳消息,如果客户端长时间未发送心跳消息,则判定其已断开连接。
- 服务器会清理断开连接的客户端,并释放相关资源。
联机大厅
联机大厅是玩家点击【进入大厅】后的第一个界面,用于管理玩家的状态、匹配对手以及查看当前在线玩家列表。
- 玩家进入大厅:
- 玩家成功连接到服务器后,发送
EnterHall
消息,服务器记录玩家的用户名和客户端ID,将其加入大厅用户字典hallClientDict
。 - 服务器向该玩家发送
AllowEnterHall
消息,确认其成功进入大厅。 - 同时,服务器通知所有在线玩家更新大厅用户列表。
- 玩家退出大厅:
- 当玩家主动退出大厅时,发送
QuitHall
消息,服务器会将其从大厅用户字典hallClientDict
中移除。 - 服务器通知所有在线玩家更新大厅用户列表。
- 大厅用户列表更新:
- 当有玩家进入或退出大厅时,服务器向所有在线玩家广播最新的
HallClients
消息,更新大厅用户列表。 - 玩家可以随时发送
RequestHallClients
消息,请求获取当前大厅用户列表。
- 玩家状态管理:
- 玩家在大厅中的状态分为“空闲”和“繁忙”。空闲状态的玩家可以接收对战请求,繁忙状态的玩家则不能。
- 服务器通过
SetHallClientIdle
方法更新玩家的状态,并在状态变化时通知所有在线玩家。
联网对战
联网对战是游戏的核心功能,玩家可以在大厅中发起对战请求,与其他玩家进行实时对战。
- 发起对战请求:
- 玩家可选择大厅中的其他空闲玩家,向其发送
SendBattleRequest
消息。 - 服务器验证目标玩家的状态,如果目标玩家空闲,则转发对战请求,并将其状态设置为“繁忙”。
- 处理对战请求:
- 目标玩家收到对战请求后,可以选择接受或拒绝。
- 如果目标玩家拒绝,服务器将其状态重置为“空闲”,并通知发起玩家对战请求被拒绝。
- 如果目标玩家接受,服务器创建一个新的战局,并将双方玩家的状态设置为“繁忙”。
- 进入对战:
- 服务器会向双方玩家发送
EnterRound
消息,通知他们进入对战界面。 - 服务器会为对战分配一个唯一的
onlineRoundIndex
,并初始化对战状态OnlineRoundState
,记录双方的玩家ID和棋盘状态。
- 对战过程:
- 对战过程中,玩家每次落子会发送
MoveInfo
消息,服务器会更新对战状态,并将落子信息转发给对手。 - 服务器不检查对战状态,判断对战是否结束的逻辑写在客户端;服务器只负责最后校验。
战局信息
- 在线战局状态管理:
- 使用
onlineRoundDict
字典管理在线战局状态。 - 收到客户端发来的表示落子信息的
MoveInfo
消息后: - 用
GetRiverClientID
从中获取某个客户端的对手客户端ID,并给对手客户端发送落子信息。 - 用
UpdateOnlineRoundState
更新指定战局中某个格子的状态。
- 联机对战结果:
- 使用
onlineRoundResultDict
字典管理客户端发送过来的联机对战结果。 - 接收到客户端发来的
OnlineRoundResult
消息后,解析对战结果数据。 - 根据
roundID
判断是否已经存在对应的战局信息: - 如果不存在,创建新的
Round
对象并存入字典。 - 如果存在,进行校验(
result
和steps
),校验通过后保存到文件并删除字典中的键值对。
- 序列化和反序列化:
- 使用
RoundManager
静态类对战局信息进行序列化和反序列化: - 序列化:
SaveRoundInfo
将Round
对象保存到文件中。 - 反序列化:
GetRoundList
从文件中读取所有战局信息并返回Round
数组。
3.协议处理
协议类型
消息ID | 数值 | 方向 | 关键行为 |
EnterHall | 1 | 客户端 → 服务端 | 携带用户名初始化连接,触发服务端分配clientID |
QuitHall | 2 | 客户端 → 服务端 | 清除用户在大厅的在线状态 |
SendBattleRequest | 3 | 客户端 ↔ 服务端 | 请求方发送目标clientID,服务端转发请求并标记目标为繁忙 |
ReplyBattleRequest | 4 | 客户端 → 服务端 | 包含接受/拒绝标志,触发战局创建或状态重置 |
EnterRound | 5 | 服务端 → 客户端 | 携带先手标识(isPrevPlayer)和战局索引(onlineRoundIndex) |
Round | 6 | 客户端 → 服务端 | 持久化保存到 RoundManager 管理的txt文件 |
RequestRoundList | 7 | 客户端 → 服务端 | 触发服务端从文件加载历史数据 |
ProvideRoundList | 8 | 服务端 → 客户端 | 携带所有历史战局的Round对象数组 |
AllowEnterHall | 11 | 服务端 → 客户端 | 返回分配的clientID(数值型标识) |
HallClients | 12 | 服务端 → 客户端 | 包含大厅用户字典(clientID-用户名映射) |
RequestHallClients | 13 | 客户端 → 服务端 | 手动请求刷新大厅列表 |
MoveInfo | 18 | 客户端 ↔ 服务端 | 包含棋子位置(pos)和战局索引(onlineRoundIndex) |
ClientQuit | 99 | 客户端 → 服务端 | 显式断开连接指令 |
HeartMessage | 100 | 客户端 → 服务端 | 维持TCP连接活性 |
OnlineRoundResult | 9 | 客户端 → 服务端 | 战局结束后,双方客户端自动向服务端发送,包含战局ID(roundID)、落子信息(steps)、胜负结果(result) |
消息处理
在
ClientSocket->HandleMessage
方法中实现,根据不同的 MessageID
来处理不同的消息类型。- 战局管理
- RoundInfo (6)
- RequestRoundList (7)
- OnlineRoundResult (9)
→ 解析战局数据 → 调用
RoundManager.SaveRoundInfo()
持久化到txt文件→ 调用
RoundManager.GetRoundList()
读取文件 → 发送ProvideRoundList
消息给客户端→ 解析对战结果 → 根据
roundID
是否在onlineRoundResultDict
中决定新增或校验 → 校验通过后保存到文件。- 大厅管理
- EnterHall (1)
- QuitHall (2)
- RequestHallClients (13)
→ 记录用户名 → 分配clientID → 发送
AllowEnterHall
→ 加入大厅字典→ 从大厅字典移除 → 触发全厅广播更新
→ 立即发送当前大厅用户字典给客户端
- 对战流程
- SendBattleRequest (3)
- ReplyBattleRequest (4)
- 拒绝:重置双方为
空闲
- 接受: → 创建
OnlineRoundState
对象 → 分配全局onlineRoundIndex
→ 标记双方为繁忙
→ 发送EnterRound
(先手标记不同) → 创建战局 - MoveInfo (18)
→ 验证目标状态 → 转发请求 → 标记目标为
繁忙
→ 校验对战合法性 → 更新棋盘状态/战局信息 → 转发给对手客户端
- 连接管理
- ClientQuit (99)
- HeartMessage (100)
→ 清除数据(大厅&战局) → 关闭Socket
→ 更新
lastHeartbeatTime
时间戳技术细节
- 状态同步:大厅用户变化(新增、删除、闲忙状态改变)时,自动向全体在线用户广播
HallClients
消息
- 心跳检测:在客户端连入后,立即开启心跳检测,用独立线程每0.1秒检查时间戳,60秒未更新则强制断开
- 战局索引:通过自增
ONLINE_ROUND_INDEX
确保全局唯一性。
- 线程锁:多个线程可能会同时访问共享资源(如
clientDict
和hallClientDict
),导致foreach
报错;使用了线程锁(lock
)来确保同一时间只有一个线程可以访问这些共享资源。
4.异常处理
客户端断线重连
//TODO
客户端部分
客户端用Unity引擎开发,其中网络模块的核心是NetManager.cs。
1.NetManager功能拆解
核心类
NetManager
作为单例组件,挂载于Unity游戏物体,且过场景不销毁,负责管理客户端全生命周期网络交互,包括连接管理、消息路由、状态同步及异常处理。单例模式
NetManager
使用单例模式确保全局只有一个实例,方便在游戏的其他模块中调用。客户端状态记录
NetManager
中的变量记录客户端的状态,包括客户端ID、用户名、是否先手、当前对战ID等。消息处理
NetManager
字典messageHandlers
存储和调用不同消息ID对应的回调函数。- 注册消息回调函数:
RegisterHandler
方法注册消息回调函数。
- 解绑消息回调函数:
UnregisterHandler
方法解绑消息回调函数。
- 触发消息回调函数:
InvokeMessageCallback
方法触发消息回调函数,支持延迟触发。
网络连接管理
NetManager
负责与服务器的连接、断开连接以及心跳消息的发送。- 启动客户端:
StartClient
方法启动客户端并连接服务器。
- 关闭客户端:
CloseClient
方法关闭客户端并发送退出消息。
发送心跳消息
SendHeartMessage
方法定期心跳消息。
- 在
Awake
生命周期函数中开启InvokeRepeating
定期发送心跳消息。
消息发送与接收
NetManager
使用两个队列sendMsgQueue
和receiveMsgQueue
来管理消息的发送和接收;在 StartClient
方法中将这两个方法加入线程池。- 发送消息:
Send
方法将消息加入发送队列。线程方法遍历消息队列,发送消息
- 接收消息:线程方法
ReceiveMsg
方法接收消息并加入接收队列
- 消息处理:
Update
生命周期函数中处理接收到的消息,并根据消息ID调用相应的回调函数。
2. OnlineGameController功能拆解
OnlineGameController
是客户端联机对战场景的核心控制器,负责管理对战过程中的棋盘状态、玩家交互、胜负判定及结果处理;与网络模块NetManager
深度集成,实现实时对战数据的同步。棋盘状态管理
通过
OnlineGrid
数组来管理棋盘的状态;OnlineGrid
对象代表格子。- 更新棋盘状态:当玩家或对手落子时,
OnlineGameController
会更新对应格子的状态,并在UI上显示相应的棋子。
玩家交互
通过
canClick
变量来控制玩家是否可以点击棋盘格子。当轮到玩家落子时,canClick
为true
,玩家可以点击棋盘上的空格子进行落子。- 玩家落子:当玩家点击一个空格子时,
OnlineGameController
会调用Move
方法,更新棋盘状态,并将落子信息发送给服务器。
- 对手落子:当接收到对手的落子信息时,
OnlineGameController
更新棋盘状态,并恢复玩家的点击权限。
胜负判定
从第5步开始,在每一步落子后都会检查当前棋盘状态,判断是否有玩家获胜或是否平局。
- 胜负检查:
CheckWin
检查棋盘的所有可能获胜情况,包括横向、纵向和斜向。
- 处理胜负结果:当检测到有玩家获胜或平局时,调用
CheckAndHandleGameResult
处理游戏结束的逻辑,包括显示结果面板、发送结果给服务器。
游戏结束处理
当游戏结束时,显示游戏结束面板,并根据胜负结果更新UI。
- 显示游戏结束面板:
ShowGameOverPanel
方法会在0.5秒后显示游戏结束面板,并根据胜负结果更新Text_Result
的文本内容。
- 保存对战数据:玩家可以点击保存按钮,将当前对战的数据保存到本地或发送给服务器。
- Author:Yuki
- URL:http://shirakoko.xyz/article/cross-chess-net
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts