Photon Server向目标客户端发消息及客户端Peer存储问题咨询
关于Photon Server客户端管理与消息发送的解决方案
嘿,我来帮你捋清楚Photon Server里客户端Peer管理和消息发送的核心问题,这俩是多人服务器开发的基础,咱们一步步拆解:
一、先解决客户端Peer存入列表的问题
你当前的ClientCollection有两个小问题:一是列表没初始化(会导致空引用异常),二是缺少线程安全处理(Photon的回调是多线程触发的,直接操作列表会有并发风险)。另外,AddNewClient的参数用PeerBase是完全正确的——因为Photon所有客户端Peer最终都继承自这个基类。
修正后的ClientCollection代码
public class ClientCollection { // 用私有列表+锁保证线程安全 private readonly List<PeerBase> _clients = new List<PeerBase>(); private readonly object _lockObj = new object(); public ClientCollection() { } public void AddNewClient(PeerBase peer) { lock(_lockObj) { // 避免重复添加同一客户端 if (!_clients.Contains(peer)) { _clients.Add(peer); Debug.Log($"新客户端已连接,当前在线数:{_clients.Count}"); } } } // 别忘了添加移除客户端的方法(断开连接时用) public void RemoveClient(PeerBase peer) { lock(_lockObj) { if (_clients.Contains(peer)) { _clients.Remove(peer); Debug.Log($"客户端已断开,当前在线数:{_clients.Count}"); } } } // 返回列表副本,避免外部直接修改原集合 public List<PeerBase> GetAllClients() { lock(_lockObj) { return new List<PeerBase>(_clients); } } }
什么时候调用AddNewClient?
在你的自定义ServerPeer类里,CreateClientPeer方法会在新客户端连接时触发,这里是添加Peer到列表的最佳时机:
public class CustomServerPeer : ServerPeerBase { private readonly ClientCollection _clientCollection; // 通过构造注入传递客户端集合(建议用单例或依赖注入框架管理) public CustomServerPeer(ClientCollection clientCollection) { _clientCollection = clientCollection; } protected override PeerBase CreateClientPeer(InitRequest initRequest) { // 创建你的自定义ClientPeer实例 var clientPeer = new CustomClientPeer(initRequest); // 把新Peer加入集合 _clientCollection.AddNewClient(clientPeer); return clientPeer; } // 客户端断开时从集合移除 protected override void OnDisconnect(PeerBase peer, DisconnectReason reasonCode, string reasonDetail) { _clientCollection.RemoveClient(peer); base.OnDisconnect(peer, reasonCode, reasonDetail); } }
二、消息发送的代码实现
Photon里发送消息主要用两种方式,根据场景选择:
1. 向单个客户端发送消息
适合一对一的交互(比如私聊、单独同步状态):
/// <summary> /// 给单个客户端发送事件通知(单向,无需回复) /// </summary> public void SendEventToClient(PeerBase peer, byte eventCode, Dictionary<byte, object> eventData) { if (peer != null && peer.IsConnected) { var sendParams = new SendParameters(); sendParams.ChannelId = 0; // 使用默认通信通道 sendParams.DeliveryMode = DeliveryMode.Reliable; // 可靠传输,确保消息送达 peer.SendEvent(eventCode, eventData, sendParams); } } /// <summary> /// 给单个客户端发送操作响应(请求-响应模式) /// </summary> public void SendOperationResponseToClient(PeerBase peer, short operationCode, Dictionary<byte, object> responseData, short returnCode = 0, string debugMessage = "") { if (peer != null && peer.IsConnected) { var response = new OperationResponse(operationCode) { ReturnCode = returnCode, DebugMessage = debugMessage, Parameters = responseData }; var sendParams = new SendParameters(); sendParams.DeliveryMode = DeliveryMode.Reliable; peer.SendOperationResponse(response, sendParams); } }
2. 向所有客户端广播消息
适合同步全局状态(比如玩家列表更新、公共事件通知):
/// <summary> /// 给所有在线客户端广播事件 /// </summary> public void BroadcastEventToAll(byte eventCode, Dictionary<byte, object> eventData) { var clients = _clientCollection.GetAllClients(); foreach (var client in clients) { if (client.IsConnected) { var sendParams = new SendParameters(); sendParams.ChannelId = 0; sendParams.DeliveryMode = DeliveryMode.Reliable; client.SendEvent(eventCode, eventData, sendParams); } } }
几个关键注意点
- 线程安全:所有操作客户端列表的代码必须加锁,Photon的回调是多线程触发的,不加锁会出现集合修改异常。
- 无效Peer清理:客户端断开时一定要从列表移除,避免持有无效引用导致发送消息失败。
- 传输模式选择:
DeliveryMode.Reliable确保消息必达但延迟稍高,DeliveryMode.Unreliable适合对实时性要求高的内容(比如玩家位置同步)。
内容的提问来源于stack exchange,提问作者古金禾




